Accept lowercase profile types and anchor sides when reading

This commit is contained in:
Danny Kim
2026-10-20 01:53:32 +09:00
parent 13efaccb17
commit bcb0e0cc70
3 changed files with 29 additions and 2 deletions
+6 -1
View File
@@ -28,6 +28,11 @@ const VERDICTS = new Map<string, [event: string, mark: string]>([
]); ]);
const PROFILE_TYPES = new Set<unknown>(["CLAUDE", "CODEX", "OPENCODE"]); const PROFILE_TYPES = new Set<unknown>(["CLAUDE", "CODEX", "OPENCODE"]);
const FAILURE_REASONS = new Set<unknown>(["SIGN_IN_FAILED", "USAGE_EXHAUSTED", "FAILED"]); const FAILURE_REASONS = new Set<unknown>(["SIGN_IN_FAILED", "USAGE_EXHAUSTED", "FAILED"]);
// Anchors written before the UPPER_SNAKE switch spell sides in lowercase.
const OLD_SIDES = new Map<unknown, Anchor["side"]>([
["new", "NEW"],
["old", "OLD"],
]);
const isProfileType = (value: unknown): value is ProfileFailure["type"] => PROFILE_TYPES.has(value); const isProfileType = (value: unknown): value is ProfileFailure["type"] => PROFILE_TYPES.has(value);
const isFailureReason = (value: unknown): value is FailureReason => FAILURE_REASONS.has(value); const isFailureReason = (value: unknown): value is FailureReason => FAILURE_REASONS.has(value);
const failureComment = (failures: ProfileFailure[]): string => const failureComment = (failures: ProfileFailure[]): string =>
@@ -56,7 +61,7 @@ function parseAnchors(text: string, sourcePath: string): Anchor[] {
const path = field(entry, "path"); const path = field(entry, "path");
const line = field(entry, "line"); const line = field(entry, "line");
const givenSide = field(entry, "side"); const givenSide = field(entry, "side");
const side = givenSide === undefined ? "NEW" : givenSide; const side = givenSide === undefined ? "NEW" : (OLD_SIDES.get(givenSide) ?? givenSide);
const body = field(entry, "body"); const body = field(entry, "body");
if (typeof path !== "string" || path === "") { if (typeof path !== "string" || path === "") {
throw new TypeError(`${at}.path: expected a repository path`); throw new TypeError(`${at}.path: expected a repository path`);
+11
View File
@@ -65,3 +65,14 @@ test("rejects malformed central credentials", async () => {
), ),
).rejects.toThrow("profiles[0].token"); ).rejects.toThrow("profiles[0].token");
}); });
test("reads lowercase profile types from older credentials", async () => {
const credentials = await parseCredentials(
JSON.stringify({
"reviewer-token": ENCRYPTED,
profiles: [{ type: "codex" }, { type: "claude", token: ENCRYPTED }],
}),
MASTER_KEY,
);
expect(credentials.profiles.map(({ type }) => type)).toStrictEqual(["CODEX", "CLAUDE"]);
});
+12 -1
View File
@@ -78,6 +78,17 @@ export async function encryptCredential(
return `v1:${encodeBase64Url(nonce)}:${encodeBase64Url(new Uint8Array(ciphertext))}`; return `v1:${encodeBase64Url(nonce)}:${encodeBase64Url(new Uint8Array(ciphertext))}`;
} }
// profiles.json files written before the UPPER_SNAKE switch spell types in lowercase.
const OLD_PROFILE_TYPES = new Map<unknown, BotProfile["type"]>([
["claude", "CLAUDE"],
["codex", "CODEX"],
["opencode", "OPENCODE"],
]);
function currentSpelling(type: unknown): unknown {
return OLD_PROFILE_TYPES.get(type) ?? type;
}
async function parseProfile(entry: unknown, index: number, masterKey: string): Promise<BotProfile> { async function parseProfile(entry: unknown, index: number, masterKey: string): Promise<BotProfile> {
const at = `profiles[${index}]`; const at = `profiles[${index}]`;
if (!isMapping(entry)) { if (!isMapping(entry)) {
@@ -88,7 +99,7 @@ async function parseProfile(entry: unknown, index: number, masterKey: string): P
throw new Error(`${at}.${key}: unknown field`); throw new Error(`${at}.${key}: unknown field`);
} }
} }
const type = field(entry, "type"); const type = currentSpelling(field(entry, "type"));
const model = field(entry, "model"); const model = field(entry, "model");
const token = field(entry, "token"); const token = field(entry, "token");
if (type !== "CLAUDE" && type !== "CODEX" && type !== "OPENCODE") { if (type !== "CLAUDE" && type !== "CODEX" && type !== "OPENCODE") {