Files
jason.woltje a8d9eecf65 organization: move packages/business to packages/organization (rename r2 stage B1)
git mv packages/business -> packages/organization (@mosaic/organization). The
old package stays as one-line re-export shims of each moved module, with
src/business.mjs re-exporting src/organization-config.mjs and src/cli.mjs
forwarding to the moved executable. BusinessError is OrganizationConfigError
(one constructor, name kept BusinessError); OrganizationError stays distinct.
The bus adapter gains organization-context.mjs (busOrganization), with
business.mjs (busBusiness) as the same function.

Import consumers, manifests, the lockfile, scripts/mosaic, stack-copy.sh
COPY_PATHS (both roots), seat sandbox RUNNER_FILES and the copy closure
walker (organization-imports.mjs, both roots required) move to the new path.
v1 serialization, v1 writers and the stage A fixtures are unchanged; the stage
A contract tests stay where they were and pass as before.
2026-11-09 11:47:48 -06:00

109 lines
4.2 KiB
JavaScript

// Shared fixtures. Every test works in its own temporary directory and
// points MOSAIC_CONFIG there; nothing reads the real ~/.config.
import { chmodSync, cpSync, mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
export const REPO = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
export const REPO_ROLES = join(REPO, "roles");
export const FAR_DATE = "2099-01-01";
export function tmp() {
return mkdtempSync(join(tmpdir(), "mosaic-business-"));
}
export function writeJson(file, value, mode = 0o600) {
mkdirSync(dirname(file), { recursive: true });
writeFileSync(file, typeof value === "string" ? value : `${JSON.stringify(value, null, 2)}\n`);
chmodSync(file, mode);
return file;
}
// A token file with a placeholder value; the package never reads it.
export function tokenFile(dir, name) {
mkdirSync(dir, { recursive: true, mode: 0o700 });
const file = join(dir, name);
writeFileSync(file, "placeholder-not-a-token");
chmodSync(file, 0o600);
return file;
}
// A copy of the shipped role files in a scratch roles directory.
export function rolesCopy(root) {
const dir = join(root, "roles");
cpSync(REPO_ROLES, dir, { recursive: true });
return dir;
}
// A complete valid business document for the shipped roles, with
// token files under `<root>/secrets`, and a project root under `<root>`.
export function businessDoc(root, id = "acme") {
const secrets = join(root, "secrets");
const ref = (role, service) => service === "gitea"
? { file: tokenFile(secrets, `${role}-gitea.token`), rotateBy: FAR_DATE }
: { file: tokenFile(secrets, `${role}-vikunja.token`), expires: FAR_DATE };
const role = (definition, botId, extra = {}) => ({
definition,
tracker: { bot: `bot-${id}-${definition}`, botId },
credentials: { gitea: ref(definition, "gitea"), vikunja: ref(definition, "vikunja") },
...extra,
});
const projectRoot = join(root, "project");
mkdirSync(projectRoot, { recursive: true });
return {
businessVersion: 1,
id,
human: "jason",
arbiters: { delivery: "pm", technical: "cto" },
projects: { stack: { root: projectRoot } },
vars: { "tracker.baseUrl": "http://127.0.0.1:3456", "gitea.baseUrl": "https://git.example", "tracker.pollSeconds": 60 },
tracker: {
sync: { bot: `bot-${id}-sync`, botId: 9, credentials: { vikunja: ref("sync", "vikunja") } },
labels: {},
},
roles: {
pm: role("pm", 3, { holder: "sage", vars: { harness: "pi", model: "claude-opus-5-5" } }),
cto: role("cto", 4, { holder: "darkwing", vars: { harness: "claude-code" } }),
coder: role("coder", 5, { vars: { harness: "pi", "limits.network": "none" } }),
reviewer: role("reviewer", 6),
// The chief of staff launches (REQ-LAUNCH-1, decision 139); it holds no
// credential and no tracker bot.
chief: { definition: "chief-of-staff" },
},
launch: launchDoc(),
};
}
// The system config file mosaic-config.mjs validates, under `root`.
export function systemConfig(root) {
const file = join(root, "config", "config.json");
writeJson(file, {
configVersion: 1,
environment: "development",
dataRoot: join(root, "data"),
execution: { backend: "docker", provider: "zai", model: "glm-5.3-flash", adapter: "mock" },
});
return file;
}
// The checked system layer, as systemVars() returns it, without spawning.
export function systemFor(root) {
return {
environment: "development",
dataRoot: join(root, "data"),
"execution.backend": "docker",
"execution.provider": "zai",
"execution.model": "glm-5.3-flash",
"execution.adapter": "mock",
};
}
// REQ-LIMIT-1 fixture policy: exact entries; these values are data, not ceilings.
export function launchDoc(by='chief',instances=['coder','reviewer'],counts={opus:2,sonnet:4}){
const models=Object.fromEntries(['claude-opus-5-5','claude-sonnet-5-5','glm-5.3-flash'].map(model=>[model,{provider:'zai',model}]));
const values={};if(counts.opus!==undefined)values['claude-opus-5-5']=counts.opus;if(counts.sonnet!==undefined)values['claude-sonnet-5-5']=counts.sonnet;
return {by,instances,models,limits:{models:{zai:values}}};
}