Correct registry validation and contain metadata reads (#1500)

This commit is contained in:
2026-09-10 16:40:16 -05:00
parent b3fa221060
commit 6335342873
12 changed files with 470 additions and 289 deletions
+91 -157
View File
@@ -1,184 +1,118 @@
// Registry record validation for the Mosaic auth/provider/harness domain.
// Increment 1 per docs/plans/2026-09-10_m20-increment1-charter.md: pure
// schema/reference validation, no I/O, no secrets, no materialization.
const ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
// Pure metadata validation. Diagnostics never include input values or unknown keys.
const ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
const REF = /^[a-z0-9][a-z0-9._-]{0,63}\/[a-z0-9][a-z0-9._-]{0,63}$/;
export const validId = value => typeof value === 'string' && ID.test(value);
const object = v => v !== null && typeof v === 'object' && !Array.isArray(v) &&
[Object.prototype, null].includes(Object.getPrototypeOf(v));
const text = v => typeof v === 'string' && v.length > 0;
const list = predicate => v => Array.isArray(v) && v.length > 0 &&
v.every(predicate) && new Set(v).size === v.length;
const types = {
version: v => v === 1,
id: validId,
string: text,
ids: list(validId),
strings: list(text),
refs: list(v => typeof v === 'string' && REF.test(v)),
object,
boolean: v => typeof v === 'boolean',
timestamp: v => typeof v === 'string' &&
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/.test(v) && Number.isFinite(Date.parse(v)),
};
const codes = { version: 'unsupported-version', id: 'invalid-id', string: 'invalid-string',
ids: 'invalid-id-list', strings: 'invalid-model-list', refs: 'invalid-account-ref-list', object: 'not-an-object',
boolean: 'invalid-boolean', timestamp: 'invalid-timestamp' };
export class ValidationError extends Error {
constructor(path, code, detail) {
super(`${path}: ${code}${detail ? `: ${detail}` : ""}`);
this.name = "ValidationError";
this.path = path;
this.code = code;
this.detail = detail ?? "";
constructor(path, code) {
super(`${path}: ${code}`);
this.name = 'ValidationError'; this.path = path; this.code = code; this.detail = '';
}
}
const fail = (errors, path, code, detail) => errors.push(new ValidationError(path, code, detail));
function isPlainObject(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
const fail = (errors, path, code) => errors.push(new ValidationError(path, code));
function shape(errors, path, value, fields, required) {
if (!object(value)) { fail(errors, path, 'not-an-object'); return false; }
if (Object.keys(value).some(k => !Object.hasOwn(fields, k))) fail(errors, path, 'unknown-field');
for (const [key, type] of Object.entries(fields)) {
if (!Object.hasOwn(value, key)) {
if (required.includes(key)) fail(errors, `${path}.${key}`, 'missing-field');
} else if (!types[type](value[key])) fail(errors, `${path}.${key}`, codes[type]);
}
return errors.length === 0;
}
// Rejects unknown fields: every record lists its allowed keys and a type map.
function checkShape(errors, path, value, allowed, requireAll) {
if (!isPlainObject(value)) {
fail(errors, path, "not-an-object");
return;
}
for (const key of Object.keys(value)) {
if (!(key in allowed)) fail(errors, `${path}.${key}`, "unknown-field");
}
for (const [key, type] of Object.entries(allowed)) {
if (!(key in value)) {
if (requireAll.includes(key)) fail(errors, `${path}.${key}`, "missing-field");
continue;
}
const v = value[key];
if (type === "id") {
if (typeof v !== "string" || !ID_PATTERN.test(v)) fail(errors, `${path}.${key}`, "invalid-id");
} else if (type === "string") {
if (typeof v !== "string" || v.length === 0) fail(errors, `${path}.${key}`, "invalid-string");
} else if (type === "accountRef[]") {
if (!Array.isArray(v) || v.length === 0 || v.some((e) => typeof e !== "string" || !/^[a-z0-9][a-z0-9._-]{0,63}\/[a-z0-9][a-z0-9._-]{0,63}$/.test(e))) {
fail(errors, `${path}.${key}`, "invalid-account-ref-list");
}
} else if (type === "string[]") {
if (!Array.isArray(v) || v.length === 0 || v.some((e) => typeof e !== "string" || !ID_PATTERN.test(e))) {
fail(errors, `${path}.${key}`, "invalid-id-list");
}
} else if (type === "record") {
if (!isPlainObject(v)) fail(errors, `${path}.${key}`, "not-an-object");
} else if (type === "timestamp") {
if (typeof v !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/.test(v)) {
fail(errors, `${path}.${key}`, "invalid-timestamp");
}
} else if (type === "boolean") {
if (typeof v !== "boolean") fail(errors, `${path}.${key}`, "invalid-boolean");
}
}
}
export function validateProvider(record) {
const errors = [];
checkShape(errors, "provider", record, {
providerVersion: "id",
id: "id",
kind: "string",
harnesses: "record",
credentialTypes: "string[]",
allowInsecureTransport: "boolean",
}, ["providerVersion", "id", "kind", "harnesses", "credentialTypes"]);
if (errors.length === 0) {
if (record.kind !== "native" && record.kind !== "custom-endpoint") {
fail(errors, "provider.kind", "unsupported-kind", record.kind);
}
for (const [harness, cfg] of Object.entries(record.harnesses)) {
if (!ID_PATTERN.test(harness)) fail(errors, `provider.harnesses.${harness}`, "invalid-id");
if (!isPlainObject(cfg)) fail(errors, `provider.harnesses.${harness}`, "not-an-object");
}
const harnessCfg = record.harnesses.pi;
if (record.kind === "custom-endpoint" && isPlainObject(harnessCfg)) {
const url = harnessCfg.baseUrl;
if (typeof url !== "string" || !/^https?:\/\//.test(url)) {
fail(errors, "provider.harnesses.pi.baseUrl", "invalid-url");
} else if (url.startsWith("http://") && record.allowInsecureTransport !== true) {
fail(errors, "provider.allowInsecureTransport", "insecure-transport-not-allowed");
if (!shape(errors, 'provider', record, { providerVersion: 'version', id: 'id', kind: 'string',
harnesses: 'object', credentialTypes: 'ids', allowInsecureTransport: 'boolean' },
['providerVersion', 'id', 'kind', 'harnesses', 'credentialTypes'])) return errors;
if (!['native', 'custom-endpoint'].includes(record.kind)) fail(errors, 'provider.kind', 'unsupported-kind');
if (record.credentialTypes.some(t => !['oauth', 'api_key', 'none'].includes(t)))
fail(errors, 'provider.credentialTypes', 'unsupported-credential-type');
if (!Object.keys(record.harnesses).length) fail(errors, 'provider.harnesses', 'empty-harnesses');
if (record.kind === 'native' && record.allowInsecureTransport === true)
fail(errors, 'provider.allowInsecureTransport', 'insecure-transport-unsupported-for-native');
for (const [id, cfg] of Object.entries(record.harnesses)) {
const p = 'provider.harnesses.entry';
if (!validId(id)) fail(errors, p, 'invalid-id');
const local = [];
if (record.kind === 'native') {
shape(local, p, cfg, { providerId: 'id' }, ['providerId']);
} else if (record.kind === 'custom-endpoint') {
if (shape(local, p, cfg, { api: 'string', baseUrl: 'string', models: 'strings' }, ['api', 'baseUrl', 'models'])) {
checkUrl(local, p, cfg.baseUrl, record.allowInsecureTransport);
}
if (!Array.isArray(harnessCfg.models) || harnessCfg.models.length === 0) {
fail(errors, "provider.harnesses.pi.models", "invalid-model-list");
}
} else if (record.kind === "native" && isPlainObject(harnessCfg) && harnessCfg.baseUrl === undefined && Array.isArray(harnessCfg.models) === false) {
// native: pi entry is providerId only; models/bases come from provider catalog
}
if (record.kind === "native" && record.allowInsecureTransport === true) {
fail(errors, "provider.allowInsecureTransport", "insecure-transport-unsupported-for-native");
}
errors.push(...local);
}
return errors;
}
function checkUrl(errors, path, value, allowHttp) {
try {
const u = new URL(value);
if (!['http:', 'https:'].includes(u.protocol) || !u.hostname || u.hash || /[\s\\]/.test(value))
fail(errors, path, 'invalid-url');
if (u.username || u.password) fail(errors, path, 'url-credentials-forbidden');
if (u.protocol === 'http:' && allowHttp !== true) fail(errors, path, 'insecure-transport-not-allowed');
} catch { fail(errors, path, 'invalid-url'); }
}
export function validateAccount(record, providerId) {
const errors = [];
checkShape(errors, "account", record, {
accountVersion: "id",
id: "id",
name: "string",
provider: "id",
type: "string",
createdAt: "timestamp",
}, ["accountVersion", "id", "name", "provider", "type", "createdAt"]);
if (errors.length === 0) {
if (providerId !== undefined && record.provider !== providerId) {
fail(errors, "account.provider", "provider-path-mismatch", `${record.provider} vs ${providerId}`);
}
if (record.type !== "oauth" && record.type !== "api_key" && record.type !== "none") {
fail(errors, "account.type", "unsupported-credential-type", record.type);
}
}
if (!shape(errors, 'account', record, { accountVersion: 'version', id: 'id', name: 'string',
provider: 'id', type: 'string', createdAt: 'timestamp' },
['accountVersion', 'id', 'name', 'provider', 'type', 'createdAt'])) return errors;
if (providerId !== undefined && record.provider !== providerId) fail(errors, 'account.provider', 'provider-path-mismatch');
if (!['oauth', 'api_key', 'none'].includes(record.type)) fail(errors, 'account.type', 'unsupported-credential-type');
return errors;
}
function accountMap(errors, path, map) {
for (const [provider, ref] of Object.entries(map ?? {})) {
if (!validId(provider) || typeof ref !== 'string' || !REF.test(ref)) fail(errors, path, 'invalid-account-ref');
else if (ref.split('/')[0] !== provider) fail(errors, path, 'account-provider-mismatch');
}
}
export function validateSettingsProfile(record) {
const errors = [];
checkShape(errors, "profile", record, {
settingsVersion: "id",
id: "id",
allowedAccounts: "accountRef[]",
providers: "string[]",
defaultAccounts: "record",
models: "record",
}, ["settingsVersion", "id", "allowedAccounts"]);
if (errors.length === 0) {
for (const [provider, account] of Object.entries(record.defaultAccounts ?? {})) {
if (!ID_PATTERN.test(provider)) fail(errors, `profile.defaultAccounts.${provider}`, "invalid-id");
if (typeof account !== "string" || !/^[a-z0-9][a-z0-9._-]{0,63}\/[a-z0-9][a-z0-9._-]{0,63}$/.test(account)) {
fail(errors, `profile.defaultAccounts.${provider}`, "invalid-account-ref");
}
}
}
if (!shape(errors, 'profile', record, { settingsVersion: 'version', id: 'id', allowedAccounts: 'refs',
providers: 'ids', defaultAccounts: 'object', models: 'object' }, ['settingsVersion', 'id', 'allowedAccounts'])) return errors;
accountMap(errors, 'profile.defaultAccounts', record.defaultAccounts);
for (const ref of Object.values(record.defaultAccounts ?? {}))
if (!record.allowedAccounts.includes(ref)) fail(errors, 'profile.defaultAccounts', 'default-account-not-enrolled');
for (const [id, models] of Object.entries(record.models ?? {}))
if (!validId(id) || !list(text)(models)) fail(errors, 'profile.models', 'invalid-model-list');
return errors;
}
export function validateSeatSelection(record) {
const errors = [];
checkShape(errors, "selection", record, {
selectionVersion: "id",
profile: "id",
accounts: "record",
updatedAt: "timestamp",
// Fork pin (gate 6): present only when this selection was pinned at fork time.
pinnedFromSession: "string",
}, ["selectionVersion", "profile"]);
if (errors.length === 0) {
for (const [provider, account] of Object.entries(record.accounts ?? {})) {
if (!ID_PATTERN.test(provider)) fail(errors, `selection.accounts.${provider}`, "invalid-id");
if (typeof account !== "string" || !/^[a-z0-9][a-z0-9._-]{0,63}\/[a-z0-9][a-z0-9._-]{0,63}$/.test(account)) {
fail(errors, `selection.accounts.${provider}`, "invalid-account-ref");
}
}
}
if (shape(errors, 'selection', record, { selectionVersion: 'version', profile: 'id', accounts: 'object',
updatedAt: 'timestamp', pinnedFromSession: 'string' }, ['selectionVersion', 'profile']))
accountMap(errors, 'selection.accounts', record.accounts);
return errors;
}
export function validateHarnessManifest(record) {
const errors = [];
checkShape(errors, "harness", record, {
harnessVersion: "id",
id: "id",
executable: "string",
adapter: "string",
compatibleRange: "string",
executionMode: "string",
materializers: "string[]",
}, ["harnessVersion", "id", "executable", "adapter", "compatibleRange", "executionMode", "materializers"]);
if (errors.length === 0) {
if (record.id !== record.executable) {
fail(errors, "harness.executable", "id-executable-mismatch", "gate 1: canonical IDs are executable names");
}
if (record.executionMode !== "container" && record.executionMode !== "host") {
fail(errors, "harness.executionMode", "unsupported-mode", record.executionMode);
}
}
if (!shape(errors, 'harness', record, { harnessVersion: 'version', id: 'id', executable: 'id',
adapter: 'id', compatibleRange: 'string', executionMode: 'string', materializers: 'ids' },
['harnessVersion', 'id', 'executable', 'adapter', 'compatibleRange', 'executionMode', 'materializers'])) return errors;
if (record.id !== record.executable) fail(errors, 'harness.executable', 'id-executable-mismatch');
if (!['container', 'host'].includes(record.executionMode)) fail(errors, 'harness.executionMode', 'unsupported-mode');
return errors;
}