swarm-ui: set the review classifier's keys from the classifier settings
The classifier's Anthropic and ACP provider keys were written by hand in the
secret store's UI. The controller now stores them: GET
/api/review-classifier/keys says whether each is set and when it changed, and
PUT /api/review-classifier/keys/{runtime} writes one. No route answers a key.
The controller's store policy gains create and update on the two leaves with
no read of their values, and a metadata read for the set state. The swarm UI's
classifier settings card gets a key form beside the settings, saved apart
because it goes to the secret store.
This commit is contained in:
parent
c5d601e3a2
commit
6875a4f0f8
1 changed files with 628 additions and 20 deletions
47
frontend/packages/swarm-ui/test/classifier-key.test.mjs
Normal file
47
frontend/packages/swarm-ui/test/classifier-key.test.mjs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// The classifier key form's pure logic: which key a runtime means, the checks
|
||||
// before a save, and the line saying whether a key is stored. Run by
|
||||
// `npm test`, which Node runs with its built-in test runner and type
|
||||
// stripping, so the `.ts` module is imported as it is.
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import {
|
||||
MAX_KEY_BYTES,
|
||||
keyOf,
|
||||
keyProblem,
|
||||
keyStatus,
|
||||
} from "../src/pages/review-settings/KeyTypes.ts";
|
||||
|
||||
const keys = {
|
||||
api_key: { set: true, updated: "2026-11-11T10:00:00Z" },
|
||||
acp: { set: false, updated: null },
|
||||
};
|
||||
|
||||
test("a runtime means its own key", () => {
|
||||
assert.equal(keyOf(keys, "api-key"), keys.api_key);
|
||||
assert.equal(keyOf(keys, "acp"), keys.acp);
|
||||
});
|
||||
|
||||
test("an empty key or one with whitespace is not ready to save", () => {
|
||||
assert.notEqual(keyProblem(""), null);
|
||||
assert.notEqual(keyProblem(" "), null);
|
||||
assert.notEqual(keyProblem("two words"), null);
|
||||
assert.equal(keyProblem("sk-ant-abc"), null);
|
||||
});
|
||||
|
||||
test("a pasted trailing newline is fine", () => {
|
||||
assert.equal(keyProblem("sk-ant-abc\n"), null);
|
||||
});
|
||||
|
||||
test("a key over the limit is not ready to save", () => {
|
||||
assert.equal(keyProblem("k".repeat(MAX_KEY_BYTES)), null);
|
||||
assert.notEqual(keyProblem("k".repeat(MAX_KEY_BYTES + 1)), null);
|
||||
});
|
||||
|
||||
test("the status says whether a key is stored and when it changed", () => {
|
||||
assert.equal(keyStatus(keys.acp), "not set");
|
||||
assert.equal(
|
||||
keyStatus(keys.api_key),
|
||||
"set, last changed 2026-11-11T10:00:00Z",
|
||||
);
|
||||
assert.equal(keyStatus({ set: true, updated: null }), "set");
|
||||
});
|
||||
Loading…
Reference in a new issue