// #1873 SP2: the five shipped starter packs. Design: starter-packs-design section 7 (contents) and // SP2 in section 8 (acceptance). Each pack loads through SP1's reader with origin shipped, holds // what section 7 lists, pins every template at today's templateHash, and records in its // expected-lines.json the flagged and credential lines each proposed hire carries against the chief. import test from "node:test"; import assert from "node:assert/strict"; import { randomUUID } from "node:crypto"; import { appendFileSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { hasModule, loadInstalled } from "../src/loader.mjs"; import { checkPack, installedStarterPacks } from "../src/starter-pack.mjs"; import { installPlugin } from "../src/install.mjs"; import { pluginHash } from "../src/pinned.mjs"; import { compositionOf, entryFromTemplate, readTemplate, templateHash } from "../../organization/src/definitions.mjs"; import { loadRole } from "../../organization/src/role.mjs"; import { buildRegistry, checkComposition } from "../../organization/src/composition.mjs"; import { credentialLines, hireDecisions } from "../../organization/src/hire-decisions.mjs"; import { organizationWriter } from "../../organization/src/organization.mjs"; import { businessDoc, rolesCopy, tmp, writeJson } from "../../organization/tests/helpers.mjs"; const HERE = dirname(fileURLToPath(import.meta.url)); const REPO = resolve(HERE, "../../.."); const ROLES = join(REPO, "roles"); const PACK_DIR = join(REPO, "packages/plugins/starter-pack"); const CHIEF = "chief-of-staff"; const OFFICERS = ["chief-executive-officer", "chief-financial-officer", "chief-information-officer", "chief-information-security-officer"]; // Section 7, item by item. Columns are labels in the order todo, in-progress, in-review, blocked, // done. Each workflow is its step roles in order. const SECTION_7 = { coding: { definitions: ["coder", "reviewer", "security-reviewer", "pm", "merge-gate", "cto"], hires: [["pm-1", "pm"], ["coder-1", "coder"], ["reviewer-1", "reviewer"]], columns: ["Backlog", "Building", "Review", "Blocked", "Merged"], layout: "kanban", workflows: { change: [["pm"], ["coder"], ["reviewer"], ["human", "merge-gate"]] }, skills: ["ms-sdlc", "ms-unslop", "ms-file-read", "ms-file-write", "ms-goal", "ms-communications"], }, "creative-writing": { definitions: ["ideas", "research", "proofreader", "fact-checker", "continuity", "companion"], hires: [["ideas-1", "ideas"], ["proofreader-1", "proofreader"], ["continuity-1", "continuity"]], columns: ["Ideas", "Drafting", "Editing", "On hold", "Finished"], layout: "kanban", workflows: { chapter: [["ideas"], ["human", "companion"], ["continuity"], ["proofreader"]] }, skills: ["ms-unslop"], }, marketing: { definitions: ["chief-marketing-officer", "research", "ideas", "proofreader", "fact-checker"], hires: [["cmo", "chief-marketing-officer"], ["research-1", "research"], ["proofreader-1", "proofreader"]], columns: ["Ideas", "In progress", "Approval", "Blocked", "Live"], layout: "kanban", workflows: { campaign: [["research"], ["ideas"], ["fact-checker"], ["proofreader"], ["human"]] }, skills: ["ms-unslop"], }, montage: { definitions: ["ideas", "research", "proofreader", "reviewer"], hires: [["ideas-1", "ideas"], ["research-1", "research"]], columns: ["Concept", "Script", "Review", "Blocked", "Final"], layout: "kanban", workflows: { episode: [["research"], ["ideas"], ["proofreader"], ["reviewer"]] }, skills: ["ms-unslop"], }, "business-management": { definitions: [...OFFICERS, "pm", "research", "continuity"], hires: [["pm-1", "pm"], ["research-1", "research"]], columns: ["To do", "Doing", "Waiting on review", "Blocked", "Done"], layout: "list", workflows: { "decision-brief": [["research"], ["pm"], OFFICERS, ["human"]] }, skills: ["ms-unslop", "ms-goal", "ms-communications"], }, }; const IDS = Object.keys(SECTION_7); const STATES = ["todo", "in-progress", "in-review", "blocked", "done"]; const moduleOf = (id) => import(pathToFileURL(join(PACK_DIR, id, "index.mjs")).href); const expectedLines = (id) => JSON.parse(readFileSync(join(PACK_DIR, id, "expected-lines.json"), "utf8")); // The lines one template carries against the chief, as the broker computes them for a hire // (hire-decisions.mjs prepare): checkComposition's flagged lines and every credential line. function linesFor(rolesDir, template) { const chief = loadRole(rolesDir, CHIEF); const { composition, flagged } = checkComposition(compositionOf(entryFromTemplate(readTemplate(rolesDir, template))), buildRegistry(), chief, { assertClean() {} }); return { flagged: JSON.parse(JSON.stringify(flagged)), credentialLines: credentialLines(composition.credentials) }; } // Every proposal of one pack against its expected-lines.json, under rolesDir. async function checkLines(id, rolesDir) { const { starterPack } = await moduleOf(id); const pack = starterPack(); const expected = expectedLines(id); assert.deepEqual(Object.keys(expected), ["chief", "hires"], id); assert.deepEqual(expected.chief, { template: CHIEF, sha256: templateHash(rolesDir, CHIEF) }, `${id}: the chief moved; recompute expected-lines.json`); assert.deepEqual(Object.keys(expected.hires), pack.hires.map((h) => h.name), id); for (const hire of pack.hires) { const want = expected.hires[hire.name]; assert.equal(want.definition, hire.definition, `${id} ${hire.name}`); assert.deepEqual({ definition: want.definition, ...linesFor(rolesDir, pack.definitions[hire.definition].template) }, want, `${id} ${hire.name}`); } } // Red before: on BASE no starter-pack module exists. test("SP2 loading starter-pack/coding finds its module, and the loader finds all five", async () => { assert.equal(hasModule("starter-pack", "coding"), true, "loading starter-pack/coding finds no module"); const { modules, refused } = await loadInstalled("starter-pack"); assert.deepEqual(refused, []); assert.deepEqual(modules.map((m) => m.name), [...IDS].sort()); }); test("SP2 each shipped pack loads through SP1 with origin shipped", async () => { const { packs, refused } = await installedStarterPacks({ rolesDir: ROLES }); assert.deepEqual(refused, []); assert.deepEqual(packs.map((p) => [p.id, p.origin]), [...IDS].sort().map((id) => [id, "shipped"])); for (const { id, pack } of packs) { const { starterPack } = await moduleOf(id); assert.deepEqual(pack, starterPack(), id); assert.deepEqual(checkPack(starterPack(), { origin: "shipped", rolesDir: ROLES, name: id }), pack, id); } }); test("SP2 each pack holds what section 7 lists", async () => { for (const id of IDS) { const want = SECTION_7[id]; const pack = (await moduleOf(id)).starterPack(); assert.equal(pack.id, id); assert.equal(pack.scope, "org", id); assert.deepEqual(Object.keys(pack.definitions), want.definitions, id); assert.deepEqual(pack.hires.map((h) => [h.name, h.definition]), want.hires, id); assert.deepEqual(pack.board.columns.map((c) => c.state), STATES, id); assert.deepEqual(pack.board.columns.map((c) => c.label), want.columns, id); assert.equal(pack.board.layout, want.layout, id); assert.deepEqual(Object.keys(pack.workflows), Object.keys(want.workflows), id); for (const [name, roles] of Object.entries(want.workflows)) { assert.deepEqual(pack.workflows[name].steps.map((s) => s.roles), roles, `${id} ${name}`); // Every step role is a definition of this pack or the user, so SP3's applyPack always // resolves it once the pack's definitions are in the organization. for (const step of pack.workflows[name].steps) { for (const role of step.roles) assert.ok(role === "human" || Object.hasOwn(pack.definitions, role), `${id} ${name} ${step.id}`); } } assert.deepEqual(pack.skills, want.skills, id); } }); test("SP2 every definition is a whole template pinned at today's templateHash", async () => { for (const id of IDS) { const pack = (await moduleOf(id)).starterPack(); for (const [name, entry] of Object.entries(pack.definitions)) { assert.deepEqual(entry, { template: name, sha256: templateHash(ROLES, name) }, `${id} ${name}`); } } }); test("SP2 starterPack returns a fresh document on every call", async () => { for (const id of IDS) { const { starterPack } = await moduleOf(id); const first = starterPack(); first.hires.length = 0; first.definitions.extra = { template: "coder", sha256: "0".repeat(64) }; assert.notEqual(starterPack().hires.length, 0, id); assert.equal(Object.hasOwn(starterPack().definitions, "extra"), false, id); } }); test("SP2 each proposed hire's lines against the chief equal expected-lines.json", async () => { for (const id of IDS) await checkLines(id, ROLES); }); test("SP2 chief-marketing-officer is the only proposal with no lines", async () => { const empty = new Set(), carrying = new Set(); for (const id of IDS) { const { hires } = expectedLines(id); for (const { definition, flagged, credentialLines: lines } of Object.values(hires)) { (flagged.length === 0 && lines.length === 0 ? empty : carrying).add(definition); } } assert.deepEqual([...empty], ["chief-marketing-officer"]); assert.deepEqual([...carrying].sort(), ["coder", "continuity", "ideas", "pm", "proofreader", "research", "reviewer"]); }); // The broker's own hire proposal (hire-decisions.mjs prepare) in an organization whose chief is // roles/chief-of-staff.json computes the same lists, messaging lines included. test("SP2 the broker's hire proposal computes the lines expected-lines.json holds", async (t) => { const root = tmp(); t.after(() => rmSync(root, { recursive: true, force: true })); const rolesDir = rolesCopy(root); const doc = businessDoc(root); doc.plugins = { harness: ["pi"] }; assert.equal(doc.roles[doc.launch.by].definition, CHIEF); writeJson(join(root, "businesses/acme.json"), doc); const writer = organizationWriter({ dir: root, rolesDir }); const service = hireDecisions({ writer, rolesDir, configDir: root, dataRoot: join(root, "data"), assertClean: () => {} }); const seen = new Set(); for (const id of IDS) { const pack = (await moduleOf(id)).starterPack(); const expected = expectedLines(id); for (const hire of pack.hires) { const ref = pack.definitions[hire.definition]; const p = await service.prepare("acme", { op: "hire", level: "org", name: `sp2-${seen.size}`, role: { template: ref.template, sha256: ref.sha256 }, route: { harness: "pi", model: "claude-opus-5-5" }, reason: hire.reason, }, { id: randomUUID(), expiresAt: "2099-01-01T00:00:00Z" }); seen.add(`${id}/${hire.name}`); const want = expected.hires[hire.name]; assert.deepEqual(JSON.parse(JSON.stringify({ flagged: p.flagged, credentialLines: p.credentialLines })), { flagged: want.flagged, credentialLines: want.credentialLines }, `${id} ${hire.name}`); } } assert.equal(seen.size, 13); }); // A template edit that moves a hash turns the shipped listing red until the pack is re-pinned, // which is the intended review point. A chief edit turns the expected lines red the same way. test("SP2 a template edit refuses the packs that pin it until they are re-pinned", async (t) => { const root = tmp(); t.after(() => rmSync(root, { recursive: true, force: true })); const rolesDir = rolesCopy(root); appendFileSync(join(rolesDir, "coder.md"), "\nOne more line.\n"); assert.notEqual(templateHash(rolesDir, "coder"), templateHash(ROLES, "coder")); await assert.rejects(installedStarterPacks({ rolesDir }), (e) => { assert.equal(e?.name, "PluginError"); assert.equal(e.code, "starter-pack-template-changed"); assert.equal(e.plugin, "coding"); return true; }); for (const id of IDS) { const doc = (await moduleOf(id)).starterPack(); if (Object.hasOwn(doc.definitions, "coder")) { assert.throws(() => checkPack(doc, { origin: "shipped", rolesDir, name: id }), (e) => e.code === "starter-pack-template-changed"); } else { checkPack(doc, { origin: "shipped", rolesDir, name: id }); } } appendFileSync(join(rolesDir, `${CHIEF}.md`), "\nOne more line.\n"); await assert.rejects(checkLines("coding", rolesDir), /the chief moved/); }); // An installed plug-in cannot take a shipped pack's name, so it cannot replace a reviewed pack. test("SP2 installing a plug-in named after a shipped pack refuses core-name", async (t) => { const root = tmp(); t.after(() => rmSync(root, { recursive: true, force: true })); for (const id of IDS) { const src = join(root, `src-${id}`), dataRoot = join(root, `data-${id}`); mkdirSync(join(src, "starter-pack"), { recursive: true }); mkdirSync(dataRoot); const doc = (await moduleOf(id)).starterPack(); writeFileSync(join(src, "starter-pack", "index.mjs"), `export const interfaceVersion = 1;\nexport function starterPack() { return ${JSON.stringify(doc)}; }\n`); const manifest = { name: id, version: "1.0.0", kinds: ["starter-pack"], interfaceVersions: { "starter-pack": 1 }, sha256: "0".repeat(64), source: { path: "fixture", version: "1.0.0" }, signature: null, tools: [], actions: [], services: [], roleTemplates: [] }; writeFileSync(join(src, "plugin.json"), JSON.stringify(manifest)); manifest.sha256 = pluginHash(src); writeFileSync(join(src, "plugin.json"), JSON.stringify(manifest)); assert.throws(() => installPlugin({ dataRoot, source: src, expectedHash: manifest.sha256 }), (e) => e?.name === "PluginError" && e.code === "core-name", id); } });