Add packages/mosaic registry schemas, validation, read-only CLI; pin pi 0.85.1 (#1499)

This commit is contained in:
2026-09-10 15:18:15 -05:00
parent 54c1231280
commit 557aba0f9e
17 changed files with 1134 additions and 151 deletions
+19
View File
@@ -0,0 +1,19 @@
{
"name": "@mosaic/registry",
"version": "0.1.0",
"private": true,
"description": "Mosaic auth/provider/harness registry schemas and validation (increment 1: schemas, validation, read-only CLI).",
"license": "UNLICENSED",
"type": "module",
"engines": { "node": ">=24" },
"bin": { "mosaic-registry": "src/cli/main.mjs" },
"exports": {
".": "./src/index.mjs",
"./ids.mjs": "./src/ids.mjs",
"./records.mjs": "./src/records.mjs",
"./registry.mjs": "./src/registry.mjs"
},
"scripts": {
"test": "node --test tests/"
}
}
+43
View File
@@ -0,0 +1,43 @@
import { loadRegistry } from "../registry.mjs";
// Read-only registry CLI: validate and list. No mutation commands (increment 1).
function usage() {
process.stdout.write(`usage: mosaic-registry <command> --registry-root <dir>
commands:
validate validate the registry tree; exit 0 valid, 1 invalid, 2 usage
list list providers, accounts, profiles (no credential fields ever)
`);
}
async function main(argv) {
const command = argv[0];
const rootIndex = argv.indexOf("--registry-root");
const root = rootIndex !== -1 ? argv[rootIndex + 1] : undefined;
if ((command !== "validate" && command !== "list") || !root) {
usage();
return 2;
}
const { entries, errors } = await loadRegistry(root);
if (command === "validate") {
if (errors.length > 0) {
for (const err of errors) process.stderr.write(`${err.path}: ${err.code}${err.detail ? `: ${err.detail}` : ""}\n`);
process.stderr.write(`${errors.length} validation error(s)\n`);
return 1;
}
process.stdout.write("valid\n");
return 0;
}
// list: strictly non-secret projection.
const providers = Object.keys(entries.providers).sort().map((id) => ({ id, kind: entries.providers[id].kind }));
const accounts = Object.keys(entries.accounts).sort().map((ref) => ({
ref,
name: entries.accounts[ref].name,
type: entries.accounts[ref].type,
}));
const profiles = Object.keys(entries.profiles).sort().map((id) => ({ id }));
process.stdout.write(`${JSON.stringify({ providers, accounts, profiles }, null, 2)}\n`);
return errors.length > 0 ? 1 : 0;
}
process.exitCode = await main(process.argv.slice(2));
+4
View File
@@ -0,0 +1,4 @@
import { validateProvider, validateAccount, validateSettingsProfile, validateSeatSelection, validateHarnessManifest, ValidationError } from "./records.mjs";
import { loadRegistry } from "./registry.mjs";
export { validateProvider, validateAccount, validateSettingsProfile, validateSeatSelection, validateHarnessManifest, ValidationError, loadRegistry };
+184
View File
@@ -0,0 +1,184 @@
// 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}$/;
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 ?? "";
}
}
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);
}
// 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 (!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");
}
}
return errors;
}
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);
}
}
return errors;
}
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");
}
}
}
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");
}
}
}
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);
}
}
return errors;
}
+106
View File
@@ -0,0 +1,106 @@
// Registry tree loading and cross-record reference validation.
// Reads a supplied registry root; never writes, never touches credentials.
import { readFile, readdir, stat } from "node:fs/promises";
import { join } from "node:path";
import { validateProvider, validateAccount, validateSettingsProfile, validateSeatSelection, validateHarnessManifest } from "./records.mjs";
export async function loadRegistry(root) {
const entries = { providers: {}, accounts: {}, profiles: {}, selections: {}, harnesses: {} };
const errors = [];
const providersDir = join(root, "auth", "providers");
for (const file of await safeList(providersDir, errors)) {
if (!file.endsWith(".json")) continue;
const id = file.slice(0, -5);
const record = await readJson(join(providersDir, file), errors);
if (record === undefined) continue;
errors.push(...validateProvider(record));
if (record.id !== id) errors.push(new (await import("./records.mjs")).ValidationError(`providers/${file}`, "id-path-mismatch", `${record.id} vs ${id}`));
if (entries.providers[id]) errors.push(new (await import("./records.mjs")).ValidationError(`providers/${file}`, "duplicate-id", id));
entries.providers[id] = record;
}
const accountsDir = join(root, "auth", "accounts");
for (const provider of await safeList(accountsDir, errors)) {
for (const accountDir of await safeList(join(accountsDir, provider), errors)) {
// account.json sits in a per-account directory; credential.json is never read.
const record = await readJson(join(accountsDir, provider, accountDir, "account.json"), errors);
if (record === undefined) continue;
errors.push(...validateAccount(record, provider));
const accountRef = `${provider}/${accountDir}`;
if (record.id !== accountDir) errors.push(new (await import("./records.mjs")).ValidationError(`accounts/${provider}/${accountDir}`, "id-path-mismatch", `${record.id} vs ${accountDir}`));
if (entries.accounts[accountRef]) errors.push(new (await import("./records.mjs")).ValidationError(`accounts/${provider}/${accountDir}`, "duplicate-id", accountRef));
entries.accounts[accountRef] = record;
}
}
const settingsDir = join(root, "auth", "settings");
for (const file of await safeList(settingsDir, errors)) {
if (!file.endsWith(".json")) continue;
const record = await readJson(join(settingsDir, file), errors);
if (record === undefined) continue;
errors.push(...validateSettingsProfile(record));
if (record.id !== file.slice(0, -5)) errors.push(new (await import("./records.mjs")).ValidationError(`settings/${file}`, "id-path-mismatch"));
entries.profiles[file.slice(0, -5)] = record;
}
const harnessesDir = join(root, "harnesses");
for (const file of await safeList(harnessesDir, errors)) {
if (!file.endsWith(".json")) continue;
const record = await readJson(join(harnessesDir, file), errors);
if (record === undefined) continue;
errors.push(...validateHarnessManifest(record));
entries.harnesses[file.slice(0, -5)] = record;
}
// Cross-record reference integrity.
for (const [ref, account] of Object.entries(entries.accounts)) {
if (!entries.providers[account.provider]) {
errors.push(new (await import("./records.mjs")).ValidationError(`accounts/${ref}`, "missing-provider", account.provider));
}
}
for (const [pid, profile] of Object.entries(entries.profiles)) {
for (const ref of profile.allowedAccounts ?? []) {
if (!entries.accounts[ref]) errors.push(new (await import("./records.mjs")).ValidationError(`profiles/${pid}`, "missing-account", ref));
}
for (const provider of profile.providers ?? []) {
if (!entries.providers[provider]) errors.push(new (await import("./records.mjs")).ValidationError(`profiles/${pid}`, "missing-provider", provider));
}
for (const [provider, ref] of Object.entries(profile.defaultAccounts ?? {})) {
if (!entries.accounts[ref]) errors.push(new (await import("./records.mjs")).ValidationError(`profiles/${pid}`, "missing-default-account", ref));
else if (!ref.startsWith(`${provider}/`)) errors.push(new (await import("./records.mjs")).ValidationError(`profiles/${pid}`, "default-account-provider-mismatch", ref));
}
}
return { entries, errors };
}
async function safeList(dir, errors) {
try {
const s = await stat(dir);
if (!s.isDirectory()) throw new Error("not-a-directory");
return await readdir(dir);
} catch {
return [];
}
}
async function readJson(path, errors) {
try {
return JSON.parse(await readFile(path, "utf8"));
} catch (err) {
errors.push(new ValidationErrorCompat(path, "invalid-json", err.message));
return undefined;
}
}
class ValidationErrorCompat extends Error {
constructor(path, code, detail) {
super(`${path}: ${code}: ${detail}`);
this.name = "ValidationError";
this.path = path;
this.code = code;
this.detail = detail;
}
}
@@ -0,0 +1 @@
{"accountVersion":"1","id":"homelab-openai","name":"Homelab OpenAI","provider":"openai-codex","type":"oauth","createdAt":"2026-09-10T00:00:00Z"}
@@ -0,0 +1 @@
{"providerVersion":"1","id":"ollama-remote","kind":"custom-endpoint","allowInsecureTransport":true,"harnesses":{"pi":{"api":"openai-completions","baseUrl":"http://ollama.internal:11434/v1","models":["qwen2.5-coder:7b"]}},"credentialTypes":["none"]}
@@ -0,0 +1 @@
{"providerVersion":"1","id":"openai-codex","kind":"native","harnesses":{"pi":{"providerId":"openai-codex"}},"credentialTypes":["oauth","api_key"]}
@@ -0,0 +1 @@
{"settingsVersion":"1","id":"research-default","allowedAccounts":["openai-codex/homelab-openai"],"providers":["openai-codex","ollama-remote"],"defaultAccounts":{"openai-codex":"openai-codex/homelab-openai"},"models":{"ollama-remote":["qwen2.5-coder:7b"]}}
@@ -0,0 +1 @@
{"harnessVersion":"1","id":"pi","executable":"pi","adapter":"pi","compatibleRange":">=0.85.1 <0.86.0","executionMode":"container","materializers":["auth-json","models-json"]}
+149
View File
@@ -0,0 +1,149 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, dirname, resolve } from "node:path";
import { spawnSync } from "node:child_process";
import { validateProvider, validateAccount, validateSettingsProfile, validateSeatSelection, validateHarnessManifest } from "../src/records.mjs";
const pkgRoot = resolve(import.meta.dirname, "..");
const cli = join(pkgRoot, "src", "cli", "main.mjs");
const validFixture = join(pkgRoot, "tests", "fixtures", "valid");
function makeRoot() {
const root = mkdtempSync(join(tmpdir(), "mosaic-registry-test-"));
return root;
}
function writeTree(root, files) {
for (const [name, content] of Object.entries(files)) {
const path = join(root, name);
mkdirSync(dirname(path), { recursive: true });
writeFileSync(path, typeof content === "string" ? content : JSON.stringify(content));
}
}
function runCli(root, command = "validate") {
return spawnSync(process.execPath, [cli, command, "--registry-root", root], { encoding: "utf8", timeout: 15000 });
}
const validTree = () => ({
"auth/providers/openai-codex.json": { providerVersion: "1", id: "openai-codex", kind: "native", harnesses: { pi: { providerId: "openai-codex" } }, credentialTypes: ["oauth", "api_key"] },
"auth/accounts/openai-codex/homelab/account.json": { accountVersion: "1", id: "homelab", name: "Homelab", provider: "openai-codex", type: "oauth", createdAt: "2026-09-10T00:00:00Z" },
"auth/settings/research.json": { settingsVersion: "1", id: "research", allowedAccounts: ["openai-codex/homelab"], providers: ["openai-codex"], defaultAccounts: { "openai-codex": "openai-codex/homelab" } },
"harnesses/pi.json": { harnessVersion: "1", id: "pi", executable: "pi", adapter: "pi", compatibleRange: ">=0.85.1 <0.86.0", executionMode: "container", materializers: ["auth-json"] },
});
test("valid fixture tree validates and lists without secrets", () => {
const r = runCli(validFixture);
assert.equal(r.status, 0, r.stderr);
assert.match(r.stdout, /^valid\n$/);
const l = runCli(validFixture, "list");
assert.equal(l.status, 0, l.stderr);
const listed = JSON.parse(l.stdout);
assert.equal(listed.providers.length, 2);
assert.equal(listed.accounts.length, 1);
assert.equal(listed.profiles.length, 1);
assert.ok(!JSON.stringify(listed).includes("credential"));
assert.ok(!JSON.stringify(listed).includes("token"));
});
test("unknown-field refuses", () => {
const errors = validateProvider({ providerVersion: "1", id: "p", kind: "native", harnesses: {}, credentialTypes: ["none"], extra: 1 });
assert.equal(errors[0].code, "unknown-field");
});
test("invalid-id refuses uppercase and traversal shapes", () => {
assert.ok(validateProvider({ providerVersion: "1", id: "BadID", kind: "native", harnesses: {}, credentialTypes: ["none"] }).some((e) => e.code === "invalid-id"));
assert.ok(validateAccount({ accountVersion: "1", id: "../escape", name: "x", provider: "p", type: "oauth", createdAt: "2026-09-10T00:00:00Z" }).some((e) => e.code === "invalid-id"));
});
test("plain-http baseUrl requires allowInsecureTransport", () => {
const base = { providerVersion: "1", id: "o", kind: "custom-endpoint", harnesses: { pi: { api: "openai-completions", baseUrl: "http://h:11434/v1", models: ["m"] } }, credentialTypes: ["none"] };
assert.ok(validateProvider(base).some((e) => e.code === "insecure-transport-not-allowed"));
assert.equal(validateProvider({ ...base, allowInsecureTransport: true }).length, 0);
const https = JSON.parse(JSON.stringify(base));
https.harnesses.pi.baseUrl = "https://h/v1";
assert.equal(validateProvider(https).length, 0);
});
test("native provider rejects allowInsecureTransport", () => {
assert.ok(validateProvider({ providerVersion: "1", id: "p", kind: "native", harnesses: {}, credentialTypes: ["none"], allowInsecureTransport: true }).some((e) => e.code === "insecure-transport-unsupported-for-native"));
});
test("unsupported credential type and kind refuse", () => {
assert.ok(validateAccount({ accountVersion: "1", id: "a", name: "x", provider: "p", type: "basic", createdAt: "2026-09-10T00:00:00Z" }).some((e) => e.code === "unsupported-credential-type"));
assert.ok(validateProvider({ providerVersion: "1", id: "p", kind: "weird", harnesses: {}, credentialTypes: ["none"] }).some((e) => e.code === "unsupported-kind"));
});
test("account provider-path mismatch refuses", () => {
assert.ok(validateAccount({ accountVersion: "1", id: "a", name: "x", provider: "other", type: "oauth", createdAt: "2026-09-10T00:00:00Z" }, "right").some((e) => e.code === "provider-path-mismatch"));
});
test("profile account refs must be provider/account shaped", () => {
const errors = validateSettingsProfile({ settingsVersion: "1", id: "s", allowedAccounts: ["justone"], providers: [], defaultAccounts: {} });
assert.ok(errors.some((e) => e.code === "invalid-account-ref-list"));
});
test("seat selection accepts fork pin field, validates account refs", () => {
assert.equal(validateSeatSelection({ selectionVersion: "1", profile: "research", accounts: { "openai-codex": "openai-codex/homelab" }, updatedAt: "2026-09-10T00:00:00Z", pinnedFromSession: "abc123" }).length, 0);
assert.ok(validateSeatSelection({ selectionVersion: "1", profile: "research", accounts: { "openai-codex": "nope" } }).some((e) => e.code === "invalid-account-ref"));
});
test("harness manifest id must equal executable (gate 1)", () => {
assert.ok(validateHarnessManifest({ harnessVersion: "1", id: "pi", executable: "claude", adapter: "x", compatibleRange: ">=1", executionMode: "container", materializers: ["m"] }).some((e) => e.code === "id-executable-mismatch"));
});
test("CLI validate: duplicate provider id across files refuses", () => {
const root = makeRoot();
try {
const tree = validTree();
tree["auth/providers/dup.json"] = { ...tree["auth/providers/openai-codex.json"] };
writeTree(root, tree);
const r = runCli(root);
assert.equal(r.status, 1);
assert.match(r.stderr, /duplicate-id|id-path-mismatch/);
} finally { rmSync(root, { recursive: true, force: true }); }
});
test("CLI validate: missing referenced provider/account refuse", () => {
const root = makeRoot();
try {
const tree = validTree();
delete tree["auth/providers/openai-codex.json"];
writeTree(root, tree);
const r = runCli(root);
assert.equal(r.status, 1);
assert.match(r.stderr, /missing-provider/);
} finally { rmSync(root, { recursive: true, force: true }); }
});
test("CLI validate: broken JSON refuses without secret echo", () => {
const root = makeRoot();
try {
const tree = validTree();
tree["auth/providers/openai-codex.json"] = "{ not json";
writeTree(root, tree);
const r = runCli(root);
assert.equal(r.status, 1);
assert.match(r.stderr, /invalid-json/);
} finally { rmSync(root, { recursive: true, force: true }); }
});
test("CLI usage errors exit 2", () => {
assert.equal(runCli(validFixture, "bogus").status, 2);
assert.equal(spawnSync(process.execPath, [cli, "validate"], { encoding: "utf8" }).status, 2);
});
test("credential.json sibling presence does not break validation and is never read", () => {
const root = makeRoot();
try {
const tree = validTree();
tree["auth/accounts/openai-codex/homelab/credential.json"] = { secretMarker: "FIXTURE-NOT-A-SECRET" };
writeTree(root, tree);
const r = runCli(root);
assert.equal(r.status, 0, r.stderr);
const l = runCli(root, "list");
assert.ok(!l.stdout.includes("FIXTURE-NOT-A-SECRET"));
} finally { rmSync(root, { recursive: true, force: true }); }
});