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

62 lines
4.2 KiB
JavaScript

// Row 54 (#1543): the seeded fixture the reads and views tests share. A
// business holds a Discord user id, token files, an environment variable
// name and bot ids; a data root holds a notifier config and a Discord
// binding with guild, channel and user ids and a token file; an agent's
// model var holds a path. A queue row's note names an id and token paths in
// the shapes real notes use (row 35: "~/.config/mosaic-dev/secrets/…").
// None may reach a response, the page or a log, nor may the temporary
// directory. Rows 9 and 11 have After entries in the stored {id, when} shape.
import assert from 'node:assert/strict';
import { cpSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { scratchRepo, genesisCommitted, mapText, MAP_ROWS } from '../../queue/tests/helpers.mjs';
import { businessDoc, writeJson } from '../../organization/tests/helpers.mjs';
export const SRC = join(import.meta.dirname, '..', 'src');
export const SNOWFLAKES = ['123456789012345678', '223456789012345678', '323456789012345678', '423456789012345678', '523456789012345678'];
export const day = n => new Date(Date.now() + n * 86400000).toISOString().slice(0, 10);
export const NOTE = `token at /srv/secret/x.token for ${SNOWFLAKES[4]}; tokens 0600 under ~/.config/mosaic-dev/secrets/mosaic-stack (receipt file:/srv/secret/r.json)`;
export const NOTE_SHOWN = 'token at <path> for <id>; tokens 0600 under <path> (receipt file:<path>)';
// A canonical scratch checkout with a committed queue, the reader script
// beside a copy of packages/queue/src, and a RELEASE file.
export function queueRepo(t) {
const rows = MAP_ROWS.map(r => r.id === 6 ? { ...r, note: NOTE } : r.id === 11 ? { ...r, after: [{ id: 9, when: 'done' }] } : r);
const repo = scratchRepo(t, { map: mapText(rows) });
genesisCommitted(repo);
mkdirSync(join(repo.root, 'packages/webui/src'), { recursive: true });
cpSync(join(SRC, 'queue-read.mjs'), join(repo.root, 'packages/webui/src/queue-read.mjs'));
writeFileSync(join(repo.root, 'RELEASE'), '0.0.99\n');
return repo;
}
// The business, notifier and binding files under one temporary base.
export function seeded(t, { business = true, notify = true } = {}) {
const base = realpathSync(mkdtempSync(join(tmpdir(), 'mosaic-webui-reads-')));
t.after(() => rmSync(base, { recursive: true, force: true }));
const configDir = join(base, 'config'), dataRoot = join(base, 'data');
const doc = businessDoc(base);
doc.vars['human.discordUserId'] = SNOWFLAKES[0];
doc.roles.coder.credentials.gitea = { env: 'CODER_GITEA_SECRET_ENV', rotateBy: day(-1) };
doc.roles.coder.credentials.vikunja.expires = day(3);
doc.roles.reviewer.credentials.vikunja.expires = day(-2);
doc.roles.reviewer.vars = { model: 'local-model' };doc.launch.models['local-model']={provider:'fixture',model:'/srv/secret/models/local.gguf'};
if (business) writeJson(join(configDir, 'businesses', 'acme.json'), doc);
if (notify) writeJson(join(dataRoot, 'notify', 'acme', 'notify.json'), { notifyVersion: 1, binding: 'jason-dm' });
writeJson(join(dataRoot, 'discord', 'jason-dm.json'), {
bindingVersion: 1, name: 'jason-dm', seat: 'sage', guildId: SNOWFLAKES[1], guildName: 'g', botUserId: SNOWFLAKES[2],
tokenFile: join(base, 'secrets', 'discord.token'), channels: [{ id: SNOWFLAKES[3], name: 'c', mode: 'open' }], users: [{ id: SNOWFLAKES[0], name: 'jason' }],
});
const system = { configVersion: 1, environment: 'development', dataRoot, execution: { backend: 'docker', provider: 'zai', model: 'glm-5.3-flash', adapter: 'mock' } };
return { base, configDir, dataRoot, system, doc };
}
// Nothing secret, no id, no temporary path, in any body.
export function clean(text, ...bases) {
for (const b of bases) assert.equal(text.includes(b), false, `response names ${b}`);
for (const s of SNOWFLAKES) assert.equal(text.includes(s), false, `response carries id ${s}`);
for (const s of ['placeholder-not-a-token', 'CODER_GITEA_SECRET_ENV', '"file"', '"env"', '"botId"', 'discordUserId', '"tokenFile"', '"guildId"', '"channels"', '/srv/secret', '~/', 'secrets/mosaic-stack']) assert.equal(text.includes(s), false, `response carries ${s}`);
}