codex@macbookpro
·
2026-04-01
policy.ts
1import type { BaaInstructionEnvelope } from "./types.js";
2
3const CONDUCTOR_TOOLS = [
4 "conversation/mode",
5 "conversation/pause",
6 "conversation/resume",
7 "describe",
8 "describe/business",
9 "describe/control",
10 "exec",
11 "files/read",
12 "files/write",
13 "status",
14 "system/pause",
15 "system/resume"
16];
17const BROWSER_LEGACY_TARGET_TOOLS = [
18 "current",
19 "send"
20];
21const DEFAULT_TARGET_TOOLS = new Map([
22 ["conductor", CONDUCTOR_TOOLS],
23 ["system", CONDUCTOR_TOOLS],
24 ["browser.claude", BROWSER_LEGACY_TARGET_TOOLS],
25 ["browser.chatgpt", BROWSER_LEGACY_TARGET_TOOLS],
26 ["browser.gemini", BROWSER_LEGACY_TARGET_TOOLS]
27]);
28
29export type BaaInstructionPolicyTargetToolsInput =
30 | ReadonlyMap<string, Iterable<string> | null | undefined>
31 | Record<string, Iterable<string> | null | undefined>;
32
33export interface BaaInstructionPolicyConfig {
34 targets?: BaaInstructionPolicyTargetToolsInput | null;
35}
36
37export interface BaaInstructionPolicy {
38 targets: ReadonlyMap<string, ReadonlySet<string>>;
39}
40
41export interface BaaInstructionPolicyDecision {
42 code: string | null;
43 message: string | null;
44 ok: boolean;
45}
46
47const DEFAULT_BAA_INSTRUCTION_POLICY = resolveBaaInstructionPolicy({
48 targets: createDefaultBaaInstructionPolicyTargetTools()
49});
50
51function normalizePolicyIdentifier(kind: "target" | "tool", value: string): string {
52 const normalized = value.trim();
53
54 if (normalized === "") {
55 throw new TypeError(`BAA instruction policy ${kind} names must be non-empty strings.`);
56 }
57
58 return normalized;
59}
60
61function normalizePolicyToolSet(
62 target: string,
63 tools: Iterable<string>
64): ReadonlySet<string> {
65 const normalizedTools = new Set<string>();
66
67 for (const tool of tools) {
68 if (typeof tool !== "string") {
69 throw new TypeError(`BAA instruction policy tools for "${target}" must be strings.`);
70 }
71
72 normalizedTools.add(normalizePolicyIdentifier("tool", tool));
73 }
74
75 return normalizedTools;
76}
77
78function iteratePolicyTargetTools(
79 input: BaaInstructionPolicyTargetToolsInput
80): Iterable<[string, Iterable<string> | null | undefined]> {
81 if (input instanceof Map) {
82 return input.entries();
83 }
84
85 return Object.entries(input);
86}
87
88export function createDefaultBaaInstructionPolicyTargetTools(): Map<string, Set<string>> {
89 return new Map(
90 [...DEFAULT_TARGET_TOOLS.entries()].map(([target, tools]) => [target, new Set(tools)])
91 );
92}
93
94export function resolveBaaInstructionPolicy(
95 config: BaaInstructionPolicyConfig | null | undefined = null
96): BaaInstructionPolicy {
97 const targetsInput = config?.targets ?? createDefaultBaaInstructionPolicyTargetTools();
98 const targets = new Map<string, ReadonlySet<string>>();
99
100 for (const [target, tools] of iteratePolicyTargetTools(targetsInput)) {
101 if (typeof target !== "string") {
102 throw new TypeError("BAA instruction policy target names must be strings.");
103 }
104
105 if (tools == null) {
106 continue;
107 }
108
109 const normalizedTarget = normalizePolicyIdentifier("target", target);
110 targets.set(normalizedTarget, normalizePolicyToolSet(normalizedTarget, tools));
111 }
112
113 return {
114 targets
115 };
116}
117
118export function evaluateBaaInstructionPolicy(
119 instruction: BaaInstructionEnvelope,
120 policy: BaaInstructionPolicy = DEFAULT_BAA_INSTRUCTION_POLICY
121): BaaInstructionPolicyDecision {
122 const supportedTools = policy.targets.get(instruction.target);
123
124 if (!supportedTools) {
125 return {
126 code: "unsupported_target",
127 message: `Target "${instruction.target}" is not supported in Phase 1.`,
128 ok: false
129 };
130 }
131
132 if (!supportedTools.has(instruction.tool)) {
133 return {
134 code: "unsupported_tool",
135 message: `Tool "${instruction.tool}" is not supported in Phase 1.`,
136 ok: false
137 };
138 }
139
140 return {
141 code: null,
142 message: null,
143 ok: true
144 };
145}