feat(business): roles v2, business and project files, variable layers (row 36, S1, darkwing)
Darkwing's round 2 candidate, approved by Filbert (#1518 comment 26730). build-r2.patch a27890d5, manifest 869168c7, 34 files, applied on HEAD and checked 34/34. Integration gate on an export of HEAD plus the patch: business 60/60 on Node 24 and 26, every package test and every scripts/test-*.sh green, test-task 98/98 with the live-provider cases. Conductor, queue, conversation and discord confirmed in git worktrees of HEAD with and without the patch, identical results. Lead decision 63 accepts the vocabulary location, the example path and the business branch. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
+13
-24
@@ -34,6 +34,7 @@ import process from "node:process";
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { BusinessError, validateRoleDocument } from "../packages/business/src/index.mjs";
|
||||
|
||||
const PROJECT_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const RUNS_DIRNAME = "runs";
|
||||
@@ -245,31 +246,18 @@ function validateTask(document, file) {
|
||||
};
|
||||
}
|
||||
|
||||
// Role contract (M18): seat-declared role authority. The tools array is a
|
||||
// ceiling — seats may narrow it, never escalate past it. network is declared
|
||||
// now and enforced when network policy lands. Strict schema: unknown keys
|
||||
// refuse, name must match the filename, wrong document kind refuses.
|
||||
// Role contract (M18, slice 1 row S1): seat-declared role authority. The
|
||||
// tools array is a ceiling - seats may narrow it, never escalate past it.
|
||||
// Version 2 adds a contract, an authority map and credential needs;
|
||||
// packages/business validates both versions. Unknown keys refuse, name must
|
||||
// match the filename, wrong document kind refuses.
|
||||
function validateRole(document, file) {
|
||||
rejectUnknownKeys(document, ["roleVersion", "name", "tools", "network"], "role");
|
||||
if (document.roleVersion !== 1) fail(2, 'role "roleVersion" must be 1');
|
||||
validateId(document.name, "role name");
|
||||
const base = path.basename(file).replace(/\.json$/, "");
|
||||
if (document.name !== base) fail(2, `role "name" (${document.name}) must match its filename (${base}.json)`);
|
||||
if (!Array.isArray(document.tools) || document.tools.length === 0) {
|
||||
fail(2, 'role "tools" must be a non-empty array of tool names');
|
||||
try {
|
||||
return validateRoleDocument(document, file);
|
||||
} catch (error) {
|
||||
if (error instanceof BusinessError) fail(error.exitCode, error.message);
|
||||
throw error;
|
||||
}
|
||||
const seen = new Set();
|
||||
for (const tool of document.tools) {
|
||||
if (!SUPPORTED_TOOLS.includes(tool)) {
|
||||
fail(2, `unsupported tool: ${JSON.stringify(tool)} (supported: ${SUPPORTED_TOOLS.join(", ")})`);
|
||||
}
|
||||
if (seen.has(tool)) fail(2, `duplicate tool in role tools: ${tool}`);
|
||||
seen.add(tool);
|
||||
}
|
||||
if (document.network !== undefined && !["none", "api-only", "open"].includes(document.network)) {
|
||||
fail(2, 'role "network" must be one of: none, api-only, open');
|
||||
}
|
||||
return { roleVersion: 1, name: document.name, tools: [...seen], network: document.network ?? "none" };
|
||||
}
|
||||
|
||||
function loadConfig() {
|
||||
@@ -671,7 +659,8 @@ switch (operation) {
|
||||
if (!target) fail(4, "usage: mosaic-task.mjs resolve-role <roleFile>");
|
||||
const file = path.resolve(target);
|
||||
const role = validateRole(readJsonFile(file, "role contract"), file);
|
||||
process.stdout.write(`MOSAIC_ROLE_TOOLS=${role.tools.join(",")}\nMOSAIC_ROLE_NETWORK=${role.network}\n`);
|
||||
const contract = role.contractPath === null ? "" : `MOSAIC_ROLE_CONTRACT=${role.contractPath}\n`;
|
||||
process.stdout.write(`MOSAIC_ROLE_TOOLS=${role.tools.join(",")}\nMOSAIC_ROLE_NETWORK=${role.network}\n${contract}`);
|
||||
process.exit(0);
|
||||
}
|
||||
case "retry":
|
||||
|
||||
Reference in New Issue
Block a user