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.
121 lines
5.6 KiB
JavaScript
121 lines
5.6 KiB
JavaScript
// Case 2 M8 (design 5.1, row 220): a binding for a seat in a library business
|
|
// runs only on the mosaic-bus engine with no tools key. The business files sit
|
|
// in the directory beside --config. An unreadable file refuses with
|
|
// discord-business-unreadable. Both verbs refuse: check runs with --offline, and
|
|
// run refuses in prepare(), before the journal is made and before any network.
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { spawnSync } from "node:child_process";
|
|
import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { makeRoot, makeRepo, makeDeployment } from "./helpers.mjs";
|
|
import { initializeControl } from "../../processing-control/src/state.mjs";
|
|
import { businessDoc, writeJson } from "../../organization/tests/helpers.mjs";
|
|
import { mappingEntry } from "../../library/tests/helpers.mjs";
|
|
|
|
const CLI = fileURLToPath(new URL("../src/cli.mjs", import.meta.url));
|
|
const MOSAIC_BUS = { provider: "mosaic-bus", model: "glm-5.3", thinking: "high" };
|
|
|
|
// overrides may be a function of the root, for fixtures that need a path under it.
|
|
function setup(overrides = {}) {
|
|
const root = makeRoot();
|
|
const repo = makeRepo(root);
|
|
const extra = typeof overrides === "function" ? overrides(root) : overrides;
|
|
const d = makeDeployment(root, extra, { tokenContent: "MTAw.abcdefghijklmnopqrstuvwxyz0123456789" });
|
|
return { root, repo, d };
|
|
}
|
|
|
|
// Writes businesses/acme.json under the deployment's config directory. A library
|
|
// business puts its roles (the seat "sage" here) in the library sets.
|
|
function business(s, { library, role = "sage", definition = "companion" }) {
|
|
const doc = businessDoc(s.root, "acme");
|
|
if (library) doc.projects = { books: mappingEntry(), ...doc.projects };
|
|
if (library) doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
|
|
doc.roles[role] = { definition };
|
|
doc.roles[role].credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
|
|
writeJson(join(s.root, "businesses", "acme.json"), doc);
|
|
}
|
|
|
|
// run stops at the processing gate first, so the deployment's control state is initialised.
|
|
function run(s) {
|
|
initializeControl(s.d.dataRoot);
|
|
return spawnSync(process.execPath, [CLI, "run", s.d.raw.name, "--config", s.d.config, "--repo", s.repo], { encoding: "utf8", timeout: 10000 });
|
|
}
|
|
|
|
function check(s) {
|
|
return spawnSync(process.execPath, [CLI, "check", s.d.raw.name, "--config", s.d.config, "--repo", s.repo, "--offline"], { encoding: "utf8", timeout: 10000 });
|
|
}
|
|
|
|
test("M8: a library seat's binding on a non-mosaic-bus engine is refused before any network", () => {
|
|
const s = setup();
|
|
business(s, { library: true });
|
|
const r = check(s);
|
|
assert.equal(r.status, 2, r.stderr);
|
|
assert.match(r.stderr, /discord-engine-not-allowed: .*its engine must be mosaic-bus, not zai/);
|
|
});
|
|
|
|
test("M8: a library seat's mosaic-bus binding that declares tools is refused, and the message names tools", () => {
|
|
const s = setup((root) => ({ engine: MOSAIC_BUS, tools: { roots: [{ name: "docs", path: join(root, "docs") }] } }));
|
|
business(s, { library: true });
|
|
const r = check(s);
|
|
assert.equal(r.status, 2, r.stderr);
|
|
assert.match(r.stderr, /discord-engine-not-allowed: .*tools key/);
|
|
});
|
|
|
|
test("M8: a library seat's mosaic-bus binding with no tools passes the engine rule", () => {
|
|
const s = setup({ engine: MOSAIC_BUS });
|
|
business(s, { library: true });
|
|
const r = check(s);
|
|
assert.doesNotMatch(r.stderr, /discord-engine-not-allowed|discord-business-unreadable/);
|
|
assert.equal(r.status, 0, r.stderr);
|
|
assert.match(r.stdout, /offline check passed/);
|
|
});
|
|
|
|
test("M8: an unreadable business file refuses check with discord-business-unreadable", () => {
|
|
const s = setup();
|
|
mkdirSync(join(s.root, "businesses"));
|
|
writeFileSync(join(s.root, "businesses", "broken.json"), "{");
|
|
chmodSync(join(s.root, "businesses", "broken.json"), 0o600);
|
|
const r = check(s);
|
|
assert.equal(r.status, 2, r.stderr);
|
|
assert.match(r.stderr, /discord-business-unreadable/);
|
|
});
|
|
|
|
test("M8: a binding on a seat that is in no library business checks clean, whatever its engine", () => {
|
|
const s = setup();
|
|
business(s, { library: false });
|
|
const r = check(s);
|
|
assert.equal(r.status, 0, r.stderr);
|
|
assert.match(r.stdout, /offline check passed/);
|
|
});
|
|
|
|
test("M8: run refuses a library seat's raw engine with exit 2, before the journal is made and before any network", () => {
|
|
const s = setup();
|
|
business(s, { library: true });
|
|
const r = run(s);
|
|
assert.equal(r.status, 2, r.stderr);
|
|
assert.match(r.stderr, /discord-engine-not-allowed: .*its engine must be mosaic-bus, not zai/);
|
|
assert.equal(existsSync(s.d.journalDir), false, "nothing was started");
|
|
});
|
|
|
|
test("M8: run refuses a library seat's mosaic-bus binding that declares tools", () => {
|
|
const s = setup((root) => ({ engine: MOSAIC_BUS, tools: { roots: [{ name: "docs", path: join(root, "docs") }] } }));
|
|
business(s, { library: true });
|
|
const r = run(s);
|
|
assert.equal(r.status, 2, r.stderr);
|
|
assert.match(r.stderr, /discord-engine-not-allowed: .*tools key/);
|
|
assert.equal(existsSync(s.d.journalDir), false);
|
|
});
|
|
|
|
test("M8: an unreadable business file refuses run with discord-business-unreadable, before the journal is made", () => {
|
|
const s = setup();
|
|
mkdirSync(join(s.root, "businesses"));
|
|
writeFileSync(join(s.root, "businesses", "broken.json"), "{");
|
|
chmodSync(join(s.root, "businesses", "broken.json"), 0o600);
|
|
const r = run(s);
|
|
assert.equal(r.status, 2, r.stderr);
|
|
assert.match(r.stderr, /discord-business-unreadable/);
|
|
assert.equal(existsSync(s.d.journalDir), false);
|
|
});
|