Files
openclaw/test/helpers/config/config-honor-audit.ts
T
Peter Steinberger edecdbd05e refactor(config): config-surface reduction tranche 3 — product consolidations (review request) (#111527)
* refactor(config): consolidate media model lists

* refactor(config): unify memory configuration

* refactor(config): consolidate TTS ownership

* refactor(config): move typing policy to agents

* refactor(config): retire product-level config surfaces

* refactor(config): share scoped tool policy type

* chore(config): refresh generated baselines

* fix(config): honor agent typing overrides

* fix(config): migrate sibling config consumers

* refactor(infra): keep base64url decoder private

* fix(config): strip invalid legacy TTS values

* chore(config): refresh rebased baseline hash

* fix(doctor): route legacy messages.tts.realtime voice to talk during tts move

* refactor(config): polish final layout names

* refactor(config): freeze retired tuning defaults

* feat(config): add fast mode default symmetry

* refactor(config): key agent entries by id

* docs(config): update final layout reference

* test(config): cover final layout migrations

* chore(config): refresh final layout baselines

* fix(config): align final layout runtime readers

* fix(config): align remaining readers

* fix(config): stabilize final layout migrations

* fix(config): finalize config projection proof

* fix(config): address final layout review

* docs(release): preserve historical config names

* fix(config): complete keyed agent migration

* fix(config): close final migration gaps

* fix(config): finish full-branch review

* fix(config): complete runtime secret detection

* fix(config): close final review findings

* fix(config): finish canonical docs and heartbeat migration

* fix(config): integrate latest main after rebase

* refactor(env): isolate test-only controls

* refactor(env): isolate build and development controls

* refactor(env): collapse process identity indirection

* refactor(env): remove duplicate config and temp aliases

* docs(env): define the operator-facing allowlist

* ci(env): ratchet production variable count

* fix(env): remove stale provider helper import

* fix(env): make ratchet sorting explicit

* test(env): keep test seam in dead-code audit

* test(env): cover ratchet growth and boundary; document surface budgets

* docs(config): document tier-eval consolidations

* docs(config): clarify speech preference ownership

* test(memory): align retired tuning fixtures

* refactor(memory): freeze engine heuristics

* refactor(config): apply tier-eval tranche

* refactor(tts): move persona shaping to providers

* refactor(compaction): move prompt policy to providers

* test(config): align hookified prompt fixtures

* chore(deadcode): classify test-only exports

* chore(github): remove unused spawn helper

* chore(deadcode): classify queue diagnostics

* chore(deadcode): remove unused lane snapshot export

* chore(plugin-sdk): ratchet consolidated surface

* fix(config): integrate latest main after rebase
2026-07-21 20:28:43 -07:00

156 lines
5.2 KiB
TypeScript

// Config honor audit helper checks config fields against expected consumers.
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { computeBaseConfigSchemaResponse } from "../../../src/config/schema-base.js";
// Config honor audit helpers that compare schema keys with proof inventories.
/** Inventory row describing where one config key is declared, merged, consumed, and tested. */
export type ConfigHonorInventoryRow = {
key: string;
schemaPaths: string[];
typePaths: string[];
mergePaths: string[];
consumerPaths: string[];
reloadPaths: string[];
testPaths: string[];
notes?: string[];
};
type ConfigHonorProofKey =
| "schemaPaths"
| "typePaths"
| "mergePaths"
| "consumerPaths"
| "reloadPaths"
| "testPaths";
/** Result of auditing one config honor inventory. */
type ConfigHonorAuditResult = {
schemaKeys: string[];
missingKeys: string[];
extraKeys: string[];
missingSchemaPaths: string[];
missingFiles: string[];
missingProofs: Array<{
key: string;
missing: ConfigHonorProofKey[];
}>;
};
const REPO_ROOT = fileURLToPath(new URL("../../../", import.meta.url));
const BASE_CONFIG_SCHEMA = computeBaseConfigSchemaResponse({
generatedAt: "2026-05-05T00:00:00.000Z",
});
/** Return true when a dotted schema path exists in the generated base config schema. */
function hasSchemaPath(schemaPath: string): boolean {
const segments = schemaPath.split(".");
let current: unknown = BASE_CONFIG_SCHEMA.schema;
for (const segment of segments) {
if (!current || typeof current !== "object") {
return false;
}
if (segment === "*") {
const wildcardTarget =
(current as { additionalProperties?: unknown; items?: unknown }).items ??
(current as { additionalProperties?: unknown }).additionalProperties;
if (!wildcardTarget || typeof wildcardTarget !== "object") {
return false;
}
current = wildcardTarget;
continue;
}
const properties = (current as { properties?: Record<string, unknown> }).properties;
if (!properties || !Object.hasOwn(properties, segment)) {
return false;
}
current = properties[segment];
}
return true;
}
/** List leaf schema keys for the requested config prefixes. */
export function listSchemaLeafKeysForPrefixes(prefixes: string[]): string[] {
const keys = new Set<string>();
for (const prefix of prefixes) {
const segments = prefix.split(".");
let current: unknown = BASE_CONFIG_SCHEMA.schema;
for (const segment of segments) {
if (!current || typeof current !== "object") {
current = null;
break;
}
if (segment === "*") {
current =
(current as { additionalProperties?: unknown; items?: unknown }).items ??
(current as { additionalProperties?: unknown }).additionalProperties ??
null;
continue;
}
current = (current as { properties?: Record<string, unknown> }).properties?.[segment] ?? null;
}
const properties = (current as { properties?: Record<string, unknown> } | null)?.properties;
if (!properties) {
continue;
}
for (const key of Object.keys(properties)) {
keys.add(key);
}
}
return [...keys].toSorted();
}
/** Audit an inventory against schema keys, proof paths, and file existence. */
export function auditConfigHonorInventory(params: {
prefixes: string[];
rows: ConfigHonorInventoryRow[];
expectedKeys?: string[];
repoRoot?: string;
}): ConfigHonorAuditResult {
const repoRoot = params.repoRoot ?? REPO_ROOT;
const schemaKeys = listSchemaLeafKeysForPrefixes(params.prefixes);
const expectedKeys = new Set(params.expectedKeys ?? schemaKeys);
const rowKeys = new Set(params.rows.map((row) => row.key));
const missingKeys = [...expectedKeys].filter((key) => !rowKeys.has(key)).toSorted();
const extraKeys = params.rows
.map((row) => row.key)
.filter((key) => !expectedKeys.has(key))
.toSorted();
const missingSchemaPaths = params.rows.flatMap((row) =>
row.schemaPaths.filter((schemaPath) => !hasSchemaPath(schemaPath)),
);
const missingFiles = params.rows.flatMap((row) => {
const files = [...row.typePaths, ...row.mergePaths, ...row.consumerPaths, ...row.testPaths];
return files
.filter((relativePath) => !fs.existsSync(path.join(repoRoot, relativePath)))
.map((relativePath) => `${row.key}:${relativePath}`);
});
const missingProofs = params.rows
.map((row) => {
const missing: ConfigHonorProofKey[] = [
row.schemaPaths.length === 0 ? "schemaPaths" : null,
row.typePaths.length === 0 ? "typePaths" : null,
row.mergePaths.length === 0 ? "mergePaths" : null,
row.consumerPaths.length === 0 ? "consumerPaths" : null,
row.reloadPaths.length === 0 ? "reloadPaths" : null,
row.testPaths.length === 0 ? "testPaths" : null,
].filter((value): value is ConfigHonorProofKey => value !== null);
return missing.length > 0 ? { key: row.key, missing } : null;
})
.filter((row): row is NonNullable<typeof row> => row !== null);
return {
schemaKeys,
missingKeys,
extraKeys,
missingSchemaPaths,
missingFiles,
missingProofs,
};
}