resolveInstance carries the organization's workflows, plus the given project's, the project's winning by name; the key is added only when there is one, so an organization without workflows digests as before. The prompt renders them under one fixed heading, "## Organization workflows", after the contract and persona and before "## This session". The chief of staff and roles holding task.create or task.assign see every step; a role a step names sees only its own steps; any other role sees nothing. Each workflow is one JSON line between <workflows-data> markers with <, >, &, line separators, C1 controls and bidi overrides escaped, so no title, description or step role can start a heading or close the block. The section says workflows grant nothing and the contract and rails win. The contract text is unchanged.
78 lines
4.3 KiB
JavaScript
78 lines
4.3 KiB
JavaScript
// Starter packs SP7 (#1873, design section 8): resolveInstance carries the organization's
|
|
// workflows, plus the given project's, the project's winning by name. An organization with no
|
|
// workflows resolves, and digests, exactly as before.
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { join } from "node:path";
|
|
import { projectFilePath, resolveInstance, validateBusinessDocument, validateProjectDocument } from "../src/index.mjs";
|
|
import { businessDoc, REPO_ROLES, systemFor, tmp } from "./helpers.mjs";
|
|
|
|
const CHANGE = { title: "Change", description: "One change from task to merge.", steps: [
|
|
{ title: "Write the task", state: "todo", roles: ["pm"] },
|
|
{ title: "Implement", state: "in-progress", roles: ["coder"] },
|
|
{ title: "Approve the merge", state: "done", roles: ["human"] },
|
|
] };
|
|
const HOTFIX = { steps: [{ title: "Patch", state: "in-progress", roles: ["coder"] }] };
|
|
const PROJECT_CHANGE = { title: "Project change", steps: [{ title: "Ship it", state: "done", roles: ["reviewer"] }] };
|
|
|
|
function load(root, mutate) {
|
|
const doc = businessDoc(root);
|
|
mutate?.(doc);
|
|
return { doc, business: validateBusinessDocument(doc, join(root, "businesses", "acme.json"), { rolesDir: REPO_ROLES }) };
|
|
}
|
|
const projectOf = (doc) => validateProjectDocument({ projectVersion: 1, id: "stack" }, projectFilePath(doc.projects.stack.root));
|
|
|
|
test("SP7 resolveInstance puts the organization's workflows on the resolved record", () => {
|
|
const root = tmp();
|
|
const { business } = load(root, (doc) => { doc.workflows = { change: CHANGE, hotfix: HOTFIX }; });
|
|
for (const instance of ["chief", "pm", "coder", "cto"]) {
|
|
const r = resolveInstance({ system: systemFor(root), business, instance });
|
|
assert.deepEqual(r.workflows, { change: CHANGE, hotfix: HOTFIX }, instance);
|
|
assert.ok(Object.isFrozen(r.workflows.change.steps[0].roles), "the record is frozen");
|
|
}
|
|
// The record holds its own copy, not the loaded business's objects.
|
|
const r = resolveInstance({ system: systemFor(root), business, instance: "chief" });
|
|
assert.notEqual(r.workflows.change, business.workflows.change);
|
|
});
|
|
|
|
test("SP7 with a project, the project's workflows join the organization's and win by name", () => {
|
|
const root = tmp();
|
|
const { doc, business } = load(root, (d) => {
|
|
d.workflows = { change: CHANGE, hotfix: HOTFIX };
|
|
d.projects.stack.workflows = { change: PROJECT_CHANGE, review: HOTFIX };
|
|
});
|
|
const system = systemFor(root);
|
|
const withProject = resolveInstance({ system, business, project: projectOf(doc), instance: "coder" });
|
|
assert.equal(withProject.project, "stack");
|
|
assert.deepEqual(withProject.workflows, { change: PROJECT_CHANGE, hotfix: HOTFIX, review: HOTFIX });
|
|
// The launcher names no project today (cli/src/launcher.mjs), so a launch sees the org's only.
|
|
const noProject = resolveInstance({ system, business, instance: "coder" });
|
|
assert.deepEqual(noProject.workflows, { change: CHANGE, hotfix: HOTFIX });
|
|
});
|
|
|
|
test("SP7 an organization with no workflows resolves and digests as before", () => {
|
|
const root = tmp();
|
|
const system = systemFor(root);
|
|
const plain = load(root).business;
|
|
// Project workflows only: a launch with no project sees none, so the record is the plain one.
|
|
const projectOnly = load(root, (d) => { d.projects.stack.workflows = { review: HOTFIX }; });
|
|
for (const instance of ["chief", "pm", "coder"]) {
|
|
const a = resolveInstance({ system, business: plain, instance });
|
|
const b = resolveInstance({ system, business: projectOnly.business, instance });
|
|
assert.ok(!Object.hasOwn(a, "workflows"), instance);
|
|
assert.ok(!Object.hasOwn(b, "workflows"), instance);
|
|
assert.equal(b.digest, a.digest, instance);
|
|
}
|
|
// With the project named, the project's workflows reach the record and change the digest.
|
|
const named = resolveInstance({ system, business: projectOnly.business, project: projectOf(projectOnly.doc), instance: "coder" });
|
|
assert.deepEqual(named.workflows, { review: HOTFIX });
|
|
});
|
|
|
|
test("SP7 a step role naming no definition stays on the record as written", () => {
|
|
const root = tmp();
|
|
const ghost = { steps: [{ title: "Survey", state: "todo", roles: ["scout", "human"] }] };
|
|
const { business } = load(root, (d) => { d.workflows = { ghost }; });
|
|
const r = resolveInstance({ system: systemFor(root), business, instance: "chief" });
|
|
assert.deepEqual(r.workflows, { ghost });
|
|
});
|