feat(model-policy): adversary role + lineage-diverse-from-worker constraint
Add `adversary` to SUPPORTED_MODEL_ROLES and a new symbolic constraint `lineage-diverse-from-worker` to SUPPORTED_MODEL_ROLE_CONSTRAINTS. Default constraints for `adversary` and `reviewer` now include `lineage-diverse-from-worker` so the reviewer/adversary CANNOT be a lineage-twin of the model that produced the artifact under review — prevents "yeah looks fine to me" rubber-stamp from same-family models. Helpers exported alongside the policy: - rootVendorFor(modelId) → "anthropic" | "openai" | "google" | "moonshot" | "mistral" | "minimax" | "zhipu" | "meituan" | "unknown" - isSameRootVendor(candidateId, workerId) → boolean (fail-open on unknown) These are the building blocks the selector needs. The actual filter wiring in auto-model-selection's selectAndApplyModel is left as a documented TODO — the function doesn't currently thread role context through, so plugging in lineage filtering needs a small refactor that is out of scope here. Tests: 24 pass (was 6 + 18 new). Coverage: role registration, constraint registration, defaults, validation, rootVendor mapping matrix, isSameRootVendor predicate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8832be0785
commit
e7cf168824
3 changed files with 204 additions and 1 deletions
|
|
@ -3,8 +3,12 @@ import { describe, test } from "vitest";
|
|||
|
||||
import {
|
||||
ModelRolePolicyValidationError,
|
||||
SUPPORTED_MODEL_ROLE_CONSTRAINTS,
|
||||
SUPPORTED_MODEL_ROLES,
|
||||
isSameRootVendor,
|
||||
normalizeRolePolicies,
|
||||
normalizeRolePolicy,
|
||||
rootVendorFor,
|
||||
validateRolePolicy,
|
||||
} from "../uok/model-role-policy.js";
|
||||
|
||||
|
|
@ -91,6 +95,11 @@ describe("model role policy", () => {
|
|||
mode: "auto",
|
||||
constraints: ["cheap", "review"],
|
||||
},
|
||||
adversary: {
|
||||
role: "adversary",
|
||||
mode: "auto",
|
||||
constraints: ["review", "lineage-diverse-from-worker"],
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
@ -101,4 +110,110 @@ describe("model role policy", () => {
|
|||
/unsupported model role "planner"/,
|
||||
);
|
||||
});
|
||||
|
||||
// ── adversary role ──────────────────────────────────────────────────────────
|
||||
|
||||
test("adversary_is_a_supported_role", () => {
|
||||
assert.ok(SUPPORTED_MODEL_ROLES.includes("adversary"));
|
||||
});
|
||||
|
||||
test("lineage_diverse_from_worker_is_a_supported_constraint", () => {
|
||||
assert.ok(
|
||||
SUPPORTED_MODEL_ROLE_CONSTRAINTS.includes("lineage-diverse-from-worker"),
|
||||
);
|
||||
});
|
||||
|
||||
test("adversary_default_constraints_include_review_and_lineage_diverse_from_worker", () => {
|
||||
const policy = normalizeRolePolicy("adversary");
|
||||
assert.deepEqual(policy, {
|
||||
role: "adversary",
|
||||
mode: "auto",
|
||||
constraints: ["review", "lineage-diverse-from-worker"],
|
||||
});
|
||||
});
|
||||
|
||||
test("reviewer_default_constraints_include_lineage_diverse_from_worker", () => {
|
||||
const policy = normalizeRolePolicy("reviewer");
|
||||
assert.ok(
|
||||
policy.constraints.includes("lineage-diverse-from-worker"),
|
||||
`expected lineage-diverse-from-worker in reviewer defaults, got: ${policy.constraints.join(", ")}`,
|
||||
);
|
||||
});
|
||||
|
||||
test("validateRolePolicy_adversary_with_lineage_diverse_from_worker_is_valid", () => {
|
||||
const result = validateRolePolicy("adversary", {
|
||||
constraints: ["lineage-diverse-from-worker"],
|
||||
});
|
||||
assert.equal(result.ok, true);
|
||||
assert.ok(
|
||||
result.policy.constraints.includes("lineage-diverse-from-worker"),
|
||||
);
|
||||
});
|
||||
|
||||
test("normalizeRolePolicies_empty_input_includes_adversary_role", () => {
|
||||
const policies = normalizeRolePolicies({});
|
||||
assert.ok("adversary" in policies, "adversary key missing from full policy map");
|
||||
assert.deepEqual(policies.adversary, {
|
||||
role: "adversary",
|
||||
mode: "auto",
|
||||
constraints: ["review", "lineage-diverse-from-worker"],
|
||||
});
|
||||
});
|
||||
|
||||
// ── rootVendorFor / isSameRootVendor ────────────────────────────────────────
|
||||
|
||||
test("rootVendorFor_maps_claude_prefix_to_anthropic", () => {
|
||||
assert.equal(rootVendorFor("claude-3-7-sonnet"), "anthropic");
|
||||
assert.equal(rootVendorFor("claude-opus-4"), "anthropic");
|
||||
});
|
||||
|
||||
test("rootVendorFor_maps_gpt_and_o_series_to_openai", () => {
|
||||
assert.equal(rootVendorFor("gpt-4o"), "openai");
|
||||
assert.equal(rootVendorFor("o1-preview"), "openai");
|
||||
assert.equal(rootVendorFor("o3-mini"), "openai");
|
||||
});
|
||||
|
||||
test("rootVendorFor_maps_gemini_prefix_to_google", () => {
|
||||
assert.equal(rootVendorFor("gemini-2.5-pro"), "google");
|
||||
assert.equal(rootVendorFor("gemma-3"), "google");
|
||||
});
|
||||
|
||||
test("rootVendorFor_maps_kimi_prefix_to_moonshot", () => {
|
||||
assert.equal(rootVendorFor("kimi-for-coding"), "moonshot");
|
||||
assert.equal(rootVendorFor("kimi-k2.6"), "moonshot");
|
||||
});
|
||||
|
||||
test("rootVendorFor_maps_minimax_prefix_to_minimax", () => {
|
||||
assert.equal(rootVendorFor("minimax-text-01"), "minimax");
|
||||
});
|
||||
|
||||
test("rootVendorFor_maps_glm_and_zai_prefix_to_zhipu", () => {
|
||||
assert.equal(rootVendorFor("glm-4"), "zhipu");
|
||||
assert.equal(rootVendorFor("zai-something"), "zhipu");
|
||||
});
|
||||
|
||||
test("rootVendorFor_maps_longcat_prefix_to_meituan", () => {
|
||||
assert.equal(rootVendorFor("longcat-v1"), "meituan");
|
||||
});
|
||||
|
||||
test("rootVendorFor_returns_unknown_for_unrecognised_model", () => {
|
||||
assert.equal(rootVendorFor("mystery-model-x"), "unknown");
|
||||
assert.equal(rootVendorFor(""), "unknown");
|
||||
});
|
||||
|
||||
test("isSameRootVendor_true_when_same_family", () => {
|
||||
assert.equal(isSameRootVendor("claude-3-5-sonnet", "claude-opus-4"), true);
|
||||
assert.equal(isSameRootVendor("gpt-4o", "o1-preview"), true);
|
||||
});
|
||||
|
||||
test("isSameRootVendor_false_when_different_family", () => {
|
||||
assert.equal(isSameRootVendor("claude-3-5-sonnet", "gpt-4o"), false);
|
||||
assert.equal(isSameRootVendor("gemini-2.5-pro", "kimi-for-coding"), false);
|
||||
});
|
||||
|
||||
test("isSameRootVendor_false_when_either_model_is_unknown", () => {
|
||||
assert.equal(isSameRootVendor("mystery-model", "claude-3-5-sonnet"), false);
|
||||
assert.equal(isSameRootVendor("claude-3-5-sonnet", "mystery-model"), false);
|
||||
assert.equal(isSameRootVendor("mystery-a", "mystery-b"), false);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -144,9 +144,11 @@ export {
|
|||
export { applyModelPolicyFilter } from "./model-policy.js";
|
||||
export {
|
||||
DEFAULT_MODEL_ROLE_CONSTRAINTS,
|
||||
isSameRootVendor,
|
||||
ModelRolePolicyValidationError,
|
||||
normalizeRolePolicies,
|
||||
normalizeRolePolicy,
|
||||
rootVendorFor,
|
||||
SUPPORTED_MODEL_ROLE_CONSTRAINTS,
|
||||
SUPPORTED_MODEL_ROLES,
|
||||
validateRolePolicy,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export const SUPPORTED_MODEL_ROLES = Object.freeze([
|
|||
"worker",
|
||||
"validator",
|
||||
"reviewer",
|
||||
"adversary",
|
||||
]);
|
||||
|
||||
/**
|
||||
|
|
@ -30,6 +31,21 @@ export const SUPPORTED_MODEL_ROLES = Object.freeze([
|
|||
*
|
||||
* Consumer: role-policy validation and autonomous route evidence writers.
|
||||
*/
|
||||
|
||||
/**
|
||||
* `lineage-diverse-from-worker` — selected model must NOT share root-vendor
|
||||
* lineage with the model that produced the artifact under review (the `worker`
|
||||
* role's resolved model). Prevents "looks fine to me" rubber-stamp by
|
||||
* lineage-twins. When the worker model is unknown or maps to "unknown" vendor,
|
||||
* this constraint is treated as a no-op (fail-open rather than blocking all
|
||||
* candidates). Enforcement is best-effort at the model-selection layer.
|
||||
*
|
||||
* TODO(lineage-diverse-from-worker): wire handler in auto-model-selection.js
|
||||
* selectAndApplyModel — after the policy gate resolves `routingEligibleModels`,
|
||||
* call resolveWorkerLineage(resolvedWorkerModelId) and filter candidates with
|
||||
* !isSameRootVendor(candidate.id, workerVendor). See rootVendorFor() in this
|
||||
* file for the canonical vendor mapping.
|
||||
*/
|
||||
export const SUPPORTED_MODEL_ROLE_CONSTRAINTS = Object.freeze([
|
||||
"coding",
|
||||
"review",
|
||||
|
|
@ -38,6 +54,7 @@ export const SUPPORTED_MODEL_ROLE_CONSTRAINTS = Object.freeze([
|
|||
"byok-allowed",
|
||||
"local-only",
|
||||
"strict-json",
|
||||
"lineage-diverse-from-worker",
|
||||
]);
|
||||
|
||||
const MODEL_ROLES = new Set(SUPPORTED_MODEL_ROLES);
|
||||
|
|
@ -63,7 +80,8 @@ export const DEFAULT_MODEL_ROLE_CONSTRAINTS = Object.freeze({
|
|||
orchestrator: Object.freeze(["long-context", "strict-json"]),
|
||||
worker: Object.freeze(["coding"]),
|
||||
validator: Object.freeze(["strict-json", "review"]),
|
||||
reviewer: Object.freeze(["review"]),
|
||||
reviewer: Object.freeze(["review", "lineage-diverse-from-worker"]),
|
||||
adversary: Object.freeze(["review", "lineage-diverse-from-worker"]),
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -233,3 +251,71 @@ export function normalizeRolePolicies(policies = {}) {
|
|||
]),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a model ID to its root-vendor label for lineage-diversity checks.
|
||||
*
|
||||
* Purpose: back the `lineage-diverse-from-worker` constraint — model selection
|
||||
* for reviewer/adversary roles must filter candidates that share a vendor with
|
||||
* the worker's resolved model.
|
||||
*
|
||||
* Returns one of: "anthropic" | "openai" | "google" | "moonshot" | "mistral" |
|
||||
* "minimax" | "zhipu" | "meituan" | "unknown". "unknown" means no filter is
|
||||
* applied (fail-open).
|
||||
*
|
||||
* Consumer: lineage-diverse-from-worker handler in the model-selection layer
|
||||
* (TODO — see constraint JSDoc above).
|
||||
*
|
||||
* @param {string} modelId
|
||||
* @returns {string}
|
||||
*/
|
||||
export function rootVendorFor(modelId) {
|
||||
const id = String(modelId ?? "").toLowerCase();
|
||||
if (id.startsWith("claude-") || id.startsWith("anthropic/")) return "anthropic";
|
||||
if (
|
||||
id.startsWith("gpt-") ||
|
||||
id.startsWith("o1-") ||
|
||||
id.startsWith("o3-") ||
|
||||
id.startsWith("o4-") ||
|
||||
id.startsWith("openai/")
|
||||
)
|
||||
return "openai";
|
||||
if (
|
||||
id.startsWith("gemini-") ||
|
||||
id.startsWith("gemma-") ||
|
||||
id.startsWith("google/") ||
|
||||
id.startsWith("google-")
|
||||
)
|
||||
return "google";
|
||||
if (id.startsWith("kimi-") || id.startsWith("moonshotai/")) return "moonshot";
|
||||
if (id.startsWith("mistral-") || id.startsWith("mistral/")) return "mistral";
|
||||
if (id.startsWith("minimax-") || id.startsWith("minimax/")) return "minimax";
|
||||
if (
|
||||
id.startsWith("glm-") ||
|
||||
id.startsWith("zai-") ||
|
||||
id.startsWith("zhipu/")
|
||||
)
|
||||
return "zhipu";
|
||||
if (id.startsWith("longcat-")) return "meituan";
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true when two model IDs share root-vendor lineage.
|
||||
*
|
||||
* Purpose: allow model-selection layer to implement the
|
||||
* `lineage-diverse-from-worker` constraint as a simple predicate.
|
||||
*
|
||||
* "unknown" vendor is treated as non-matching so it never blocks candidates
|
||||
* (fail-open for unrecognised models).
|
||||
*
|
||||
* @param {string} candidateModelId
|
||||
* @param {string} workerModelId
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isSameRootVendor(candidateModelId, workerModelId) {
|
||||
const a = rootVendorFor(candidateModelId);
|
||||
const b = rootVendorFor(workerModelId);
|
||||
if (a === "unknown" || b === "unknown") return false;
|
||||
return a === b;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue