Files
stack/packages/organization/tests/instance-credential-transitions.test.mjs
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

71 lines
4.2 KiB
JavaScript

// Row 285 (#1790), mos-1585/1586: only creative-to-creative changes may retain
// the same declared service set. No reference is created, moved or minted by a change.
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { instanceSettings } from '../src/instance-settings.mjs';
import { businessDoc, rolesCopy, systemFor, tmp, writeJson } from './helpers.mjs';
function fixture(t) {
const root = tmp();
t.after(() => rmSync(root, { recursive: true, force: true }));
const rolesDir = rolesCopy(root), dir = join(root, 'config');
const companion = JSON.parse(readFileSync(join(rolesDir, 'companion.json'), 'utf8'));
for (const [name, authority, credentials] of [
['creative-host', companion.authority, [...companion.credentials, { service: 'host' }]],
['plain-gitea', { withinRole: ['message.send'], crossRole: [] }, companion.credentials],
['creative-vikunja', companion.authority, [{ service: 'vikunja', scopes: { tasks: ['read_one'] } }]],
]) writeJson(join(rolesDir, `${name}.json`), { ...companion, name, authority, credentials });
const doc = businessDoc(root);
doc.roles.bean = { definition: 'companion', credentials: { gitea: { ...doc.roles.coder.credentials.gitea } } };
const file = writeJson(join(dir, 'businesses/acme.json'), doc);
const settings = instanceSettings({ dir, rolesDir, system: systemFor(root) });
const change = definition => settings.set('acme', { revision: settings.read('acme').revision, instance: 'bean', op: 'change', definition }, 'jason');
return { doc, file, settings, change };
}
// Keeping the old blanket credential fence breaks this supported change.
test('row 285 P: creative equal-service changes preserve every existing credential reference', t => {
const f = fixture(t), before = JSON.parse(readFileSync(f.file, 'utf8')).roles.bean.credentials;
f.change('ideas');
const after = JSON.parse(readFileSync(f.file, 'utf8')).roles.bean;
assert.equal(after.definition, 'ideas');
assert.deepEqual(after.credentials, before);
assert.deepEqual(Object.keys(after.credentials), ['gitea']);
assert.equal(after.credentials.gitea.store, undefined);
});
// Host has no file reference, so ordinary business validation alone cannot guard this set change.
test('row 285 P: creative declared-service mismatch refuses even when host has no reference', t => {
const f = fixture(t), before = readFileSync(f.file);
assert.throws(() => f.change('creative-host'), e => e.code === 'organization-credentials-out-of-scope');
assert.deepEqual(readFileSync(f.file), before);
});
// Letting equal sets bypass the non-creative base fence breaks this refusal.
test('row 285 P: an equal-service non-creative target keeps the base credential refusal', t => {
const f = fixture(t), before = readFileSync(f.file);
assert.throws(() => f.change('plain-gitea'), e => e.code === 'organization-credentials-out-of-scope');
assert.deepEqual(readFileSync(f.file), before);
});
// Running the credential fence ahead of validation changes the base BusinessError.
test('row 285 P: business validation precedes the creative credential fence', t => {
const f = fixture(t), before = readFileSync(f.file);
assert.throws(() => f.change('security-reviewer'), e => e.name === 'BusinessError');
assert.deepEqual(readFileSync(f.file), before);
});
// Removing creative addability, or admitting host/Vikunja there, breaks these literal entries.
test('row 285 P: listing derives creative from authority and accepts only existing gitea minting', t => {
const f = fixture(t), definitions = f.settings.read('acme').definitions;
assert.deepEqual(definitions.companion, { services: ['gitea'], accepted: true });
assert.deepEqual(definitions.ideas, { services: ['gitea'], accepted: true });
assert.deepEqual(definitions['creative-host'], { services: ['gitea', 'host'], accepted: false });
assert.deepEqual(definitions['creative-vikunja'], { services: ['vikunja'], accepted: false });
assert.deepEqual(definitions['plain-gitea'], { services: ['gitea'], accepted: false });
assert.deepEqual(definitions.coder, { services: ['gitea', 'vikunja'], accepted: false });
assert.equal(definitions['chief-of-staff'].accepted, false);
});