64 lines
3.6 KiB
JavaScript
64 lines
3.6 KiB
JavaScript
// Row 143 (#1637, REQ-PROJ-4, REQ-ASSIST-3): the role contracts for the review of a human spec save. The
|
|
// pm and the cto answer the bus's notice to the chief of staff; the assistant tells the user the result in
|
|
// its own words. The text tests pin the files the stack runs, as assistant.test.mjs does.
|
|
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { createHash } from "node:crypto";
|
|
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { REPO_ROLES } from "./helpers.mjs";
|
|
|
|
const contract = (name) => readFileSync(join(REPO_ROLES, `${name}.md`), "utf8");
|
|
|
|
test("the pm and the cto check a saved spec against settled decisions and work in flight, and answer with a BLOCK or an accept", () => {
|
|
for (const name of ["pm", "cto"]) {
|
|
const text = contract(name);
|
|
assert.match(text, /the human saved\s+`spec\/north-star\.md`/, name);
|
|
assert.match(text, /`read_spec`/, name);
|
|
assert.match(text, /decisions\s+already\s+settled/, name);
|
|
assert.match(text, /in\s+flight/, name);
|
|
assert.match(text, /chief\s+of\s+staff\s+once/, name);
|
|
assert.match(text, /class\s+`BLOCK`\s+naming\s+each\s+conflict/, name);
|
|
assert.match(text, /class\s+`INFO`\s+that\s+begins\s+"Accept"/, name);
|
|
assert.match(text, /A\s+BLOCK\s+doesn't\s+revert\s+the\s+commit/, name);
|
|
assert.match(text, /the\s+assistant\s+tells\s+the\s+user/, name);
|
|
}
|
|
});
|
|
|
|
test("the chief of staff relays a BLOCK or an accept to the assistant as a reply to the message that carried it", () => {
|
|
const text = contract("chief-of-staff");
|
|
assert.match(text, /answers\s+the\s+bus's\s+notice\s+of\s+a\s+saved\s+spec\s+with\s+a\s+BLOCK\s+or\s+an\s+accept/);
|
|
assert.match(text, /relay\s+it\s+to\s+the\s+assistant\s+with\s+`send_message`\s+`in_reply_to`\s+the\s+message\s+that\s+carried\s+it/);
|
|
assert.match(text, /The\s+bus\s+refuses\s+a\s+relay\s+without\s+that\s+link/);
|
|
assert.match(text, /A\s+BLOCK\s+doesn't\s+revert\s+the\s+commit/);
|
|
});
|
|
|
|
test("the assistant summarizes a spec review in its own words and doesn't repeat the chief of staff's text", () => {
|
|
const text = contract("assistant");
|
|
assert.match(text, /review\s+of\s+the\s+user's\s+saved\s+spec\s+change,\s+summarize\s+it\s+for\s+the\s+user\s+in\s+your\s+own\s+words/);
|
|
assert.match(text, /Don't\s+repeat\s+the\s+chief\s+of\s+staff's\s+text\s+or\s+quote\s+it\s+back/);
|
|
assert.match(text, /A\s+BLOCK\s+doesn't\s+undo\s+the\s+user's\s+commit/);
|
|
// The user's own rulings still go to the chief of staff verbatim; only a review result is summarized.
|
|
assert.match(text, /Relay each ruling the user gives verbatim to the chief\s+of\s+staff/);
|
|
});
|
|
|
|
// TI: exact authorized contract wording, independently pinned for review.
|
|
test("TI contracts point at the generated and live caller rails", () => {
|
|
const pins = {
|
|
"coder": "9a31e19cbea5953fd9598def446e0e5e351869498f593f46468cf03fc0e2759b",
|
|
"cto": "c4c53b00eea1a6c73c90f2b5a9e54960848972a517c4e6ee47198e91dc8ddf45",
|
|
"merge-gate": "8d39144cab994781cadf7ac0dbfc41a1dd8a3c59ef66d916c79e9d63324572ad",
|
|
"pm": "0f7cbae8085709ab2907483543681ff7dc002ac2f9fea14d0b840213e9cf6d56",
|
|
"reviewer": "5269da2de779f3e399838a1c8357201862a4bd25c03de71ca8ebb31978dea6b7",
|
|
"security-reviewer": "eb664413c47d405ffb593cf15d3278217f09c74ddef0a179dcf22d9bf041378a",
|
|
};
|
|
for (const [name, hash] of Object.entries(pins)) {
|
|
const text = contract(name);
|
|
assert.equal(createHash('sha256').update(text).digest('hex'), hash, name);
|
|
assert.match(text, /"Your\s+rails"/);
|
|
assert.match(text, /`my_rails`/);
|
|
assert.doesNotMatch(text, /launch record you\s+received/);
|
|
}
|
|
});
|