fix(harness): expose trusted assistant coordinator context (#797)
This commit is contained in:
1 parent
29a3cc6323
commit
2f1cef4c1d
8 files changed
+108
-4
No files matched your search
@@ -6201,3 +6201,9 @@ Before: isolated checkout at 05fdfef9ea1fdddf58c725c04f229ca8559fc054, branch fi
|
||||
## 2026-11-05 — MVP 3 relay fixes (#797 / #1839), author phase completed
|
||||
|
||||
After: assistant addresses the configured chief instance; role-send receipts derive provenance from the stored message and existing reply-chain checks; PM and chief guidance describes the existing bound bootstrap decision and request-based staffing before task creation. No role JSON, schema, policy or alias change. F2 whole-file regression was red (7 pass, 1 fail: null relay receipt) then green (8 pass), with unlinked, wrong-recipient and caller-supplied-ID negatives; threaded reply file passed 20/20. Final whole-suite gates: bus on Node 24.22.1 1596/1596; harness 242/242; business 307/307; 37 additional broker-importer files 512/512; zero failures, cancellations or skips. Original Node 26 bus attempt was interrupted after project-files stalled in a socket send: 1593 pass, one cancelled; that whole file passed 8/8 on Node 24.22.1 and the complete bus suite was rerun. Scratch TMPDIR, exact commands and logs: /mnt/storage/scratch/mosaic-lead/mvp3/relay-fix-r1-report.md. Local commit authorized; independent review and all publication or live actions remain outside this author phase.
|
||||
|
||||
## 2026-11-05 — MVP 3 relay R2 trusted coordinator context (#797 / #1839)
|
||||
|
||||
Before: isolated branch fix/mvp3-relay-797-r2 from 29a3cc63238307d8644447c67a0364a6b03dfa36. Independent R1 review's sole CHANGES finding: owner-started assistants could not discover an arbitrary configured chief key, especially offline. Direct R2 charter authorizes trusted generated routing context, offline swapped-key integration evidence, required tests, append-only records and a local commit; no push or live action. Queue read refused linked worktree, then canonical read returned nothing; no queue mutation.
|
||||
|
||||
After: assistant-only resolved coordinator context comes from validated launch.by and the coordinator's optional configured identity.name. The generated prompt names the exact instance key and role guidance uses it explicitly; no-launch context reports a blocker. Broker source, messaging policy, recipient checks and role JSON are unchanged. Config-to-bundle-to-broker integration covers owner-launched assistant, offline chief, violet/opal definition swap, descriptive name, ignored session override, correct stored recipient/provenance, forbidden CIO and definition-alias refusals. Required whole-file gate 321/321 and additional content/path/bundle checks 124/124 on Node 24.22.1; final touched bundle file 24/24, all zero failures/cancellations/skips. Existing digest machinery covers assistant coordinator and prompt bytes; no literal changed-source hash pin required an update. Original reviewer probes 12/13 because their F1 deepEqual asserts the diagnosed defect; preserved original and a scratch copy changing only that assertion to notDeepEqual passed 13/13, including F1. Initial added fixture runs had two setup mistakes (missing store directory, thread claims absent from agents); corrected. Initial related run 79/124 had missing local dependencies and one Claude style-hook assertion; npm ci from the unchanged lockfile followed by the same whole-group rerun passed 124/124. Scratch evidence: /mnt/storage/scratch/tmp/0T6C89; exact commands and limits: /mnt/storage/scratch/mosaic-lead/mvp3/relay-fix-r2-report.md. Local commit only; independent exact-candidate review remains.
|
||||
@@ -2066,3 +2066,5 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-11-04 | Mos, skill integrator | #818 / Gitea #1845 | Integrated exact independently approved ms-ux-improvement candidate02 into source;3hashes/validator/whitespace pass and author histories preserved. Separate commit from MVP; its activation target staysbd8120318. No install, activation, publication or live action.
|
||||
2026-11-05 | Codex implementation sub-agent | #797 / #1839 MVP 3 relay fixes | Isolated author work on fix/mvp3-relay-797 at 05fdfef9ea1fdddf58c725c04f229ca8559fc054; queue refused linked worktree; local commit only, no live or push authority. Evidence: /mnt/storage/scratch/mosaic-lead/mvp3/relay-fix-r1-report.md.
|
||||
2026-11-05 | Codex implementation sub-agent | #797 / #1839 MVP 3 relay fixes author handoff | Four bounded fixes; F2 red/green retained; final bus, harness, business and direct-importer gates 2657/2657 with zero failures/cancellations/skips. Local commit only; independent review remains. Full evidence: /mnt/storage/scratch/mosaic-lead/mvp3/relay-fix-r1-report.md.
|
||||
2026-11-05 | Codex implementation sub-agent | #797 / #1839 relay R2 trusted coordinator context | Chartered local branch fix/mvp3-relay-797-r2 from 29a3cc63238307d8644447c67a0364a6b03dfa36; isolated config-to-bundle-to-broker correction, no push or live authority. Queue refused linked worktree; canonical read returned nothing. Evidence: /mnt/storage/scratch/mosaic-lead/mvp3/relay-fix-r2-report.md.
|
||||
2026-11-05 | Codex implementation sub-agent | #797 / #1839 relay R2 author handoff | Trusted configured-coordinator discovery with offline arbitrary-key integration; required 321/321, related 124/124, final touched bundle 24/24, corrected reviewer acceptance probes 13/13. Original probe preserved with obsolete equality assertion documented. Local commit only; independent review remains. Evidence: /mnt/storage/scratch/mosaic-lead/mvp3/relay-fix-r2-report.md.
|
||||
@@ -335,6 +335,13 @@ outside the vocabulary. `launch` is set exactly when `classify(record,
|
||||
`role.launch` for the launcher, `launch` is null. A launcher should
|
||||
still ask `classify`, not read `launch` alone.
|
||||
|
||||
An assistant also receives `coordinator`: the validated business's `launch.by`
|
||||
as `instance`, and that instance's configured `identity.name` as `name` (or
|
||||
null). With no launch block, `coordinator` is null. This routing context is
|
||||
included in the resolved digest and the generated session prompt; it does not
|
||||
grant authority. The name is descriptive, not a broker display-name alias;
|
||||
the assistant addresses messages to the exact instance key.
|
||||
|
||||
Errors are `BusinessError` with `exitCode` 2 (invalid) or 4 (a required
|
||||
file missing). The CLI adds 3 for a system config problem and 4 for usage.
|
||||
|
||||
|
||||
@@ -92,6 +92,13 @@ export function resolveInstance({ system, business, project = null, instance })
|
||||
credentials: Object.fromEntries(Object.entries(entry.credentials).filter(([kind]) => !isHostKind(kind))),
|
||||
tracker: entry.tracker,
|
||||
launch,
|
||||
// #797 relay R2: an owner-started assistant cannot infer its coordinator
|
||||
// from launchedBy or running claims. Only the validated business chooses
|
||||
// this address; it grants no launch authority or messaging exception.
|
||||
...(definition.name === "assistant" ? { coordinator: business.launch ? {
|
||||
instance: business.launch.by,
|
||||
name: business.roles[business.launch.by].identity?.name ?? null,
|
||||
} : null } : {}),
|
||||
// REQ-HARN-2: the search-source plug-in the organization file switches on, by name.
|
||||
webSource: business.plugins?.["search-source"] ?? null,
|
||||
// The role file's skill names. The bundle resolves each against <dataRoot>/skills-enabled.
|
||||
|
||||
@@ -153,7 +153,8 @@ test("the contract passes work to the chief of staff and directs no one", () =>
|
||||
assert.match(text, /don't direct\s+other agents/);
|
||||
assert.match(text, /create or assign\s+tasks/);
|
||||
assert.match(text, /launch\s+sessions/);
|
||||
assert.match(text, /business's configured chief of staff instance, using\s+its instance key or name/);
|
||||
assert.match(text, /exact instance key in the generated `Configured coordinator`\s+line under `This session`/);
|
||||
assert.match(text, /Use that key as send_message\.to for requests\s+and rulings/);
|
||||
assert.doesNotMatch(text, /addressed to the `chief-of-staff` role/);
|
||||
// Round 1 F1: rulings go to the chief of staff, never to the role they concern (REQ-ASSIST-1, REQ-ASSIST-2).
|
||||
assert.match(text, /Relay each ruling the user gives verbatim to the chief\s+of\s+staff,\s+naming\s+whose\s+ruling\s+it\s+is\s+and\s+which\s+role\s+it\s+concerns/);
|
||||
|
||||
@@ -268,6 +268,9 @@ function prompt({ contract, resolved, session, tools, untyped, builtins, persona
|
||||
"",
|
||||
`- Business: ${resolved.business}. Role instance: ${resolved.instance} (definition ${resolved.definition}). Run: ${session.run}.`,
|
||||
`- Launched by: ${session.launchedBy}. Harness: ${session.harness}, model ${session.model}.`,
|
||||
...(resolved.definition === "assistant" ? [resolved.coordinator
|
||||
? `- Configured coordinator: instance key ${JSON.stringify(resolved.coordinator.instance)}${resolved.coordinator.name ? ` (configured identity name: ${JSON.stringify(resolved.coordinator.name)})` : ""}. Use this exact instance key as send_message.to for business requests and rulings; the broker checks every send.`
|
||||
: "- Configured coordinator: none. Report the missing business launcher configuration to the user; do not guess a recipient."] : []),
|
||||
`- Workspace: ${session.workspace}. File tools work inside it only.`,
|
||||
...checkoutLines(session.checkouts ?? []),
|
||||
// Row 89 (#1583): the names the harness gives its built-ins, as the manifest lists them.
|
||||
|
||||
@@ -6,12 +6,88 @@ import { join } from "node:path";
|
||||
import { BundleError, buildBundle, sessionModel, SOURCES } from "../src/bundle.mjs";
|
||||
import { HARNESS, REPO_SKILLS, resolvedFor, scratch } from "./helpers.mjs";
|
||||
import { decide } from "../src/gate.mjs";
|
||||
import { resolveInstance, validateBusinessDocument } from "../../business/src/index.mjs";
|
||||
import { businessDoc, REPO_ROLES, systemFor } from "../../business/tests/helpers.mjs";
|
||||
import { Store } from "../../bus/src/store.mjs";
|
||||
import { Broker } from "../../bus/src/broker.mjs";
|
||||
|
||||
const UNSLOP_DIR = new URL("../../../tools/unslop-hook/", import.meta.url).pathname;
|
||||
|
||||
const sha = (p) => createHash("sha256").update(readFileSync(p)).digest("hex");
|
||||
const mode = (p) => statSync(p).mode & 0o777;
|
||||
|
||||
// #797 relay R2: no running chief or launchedBy hint is needed to discover
|
||||
// an arbitrary coordinator key. Exercise config -> resolve -> bundle -> bus.
|
||||
test("an owner-started assistant identifies and can address the configured offline coordinator", (t) => {
|
||||
const root = scratch(t);
|
||||
const doc = businessDoc(root);
|
||||
delete doc.roles.chief;
|
||||
doc.roles.assistant = { definition: "assistant", vars: { harness: "pi", model: "glm-5.3-flash", thinking: "high" } };
|
||||
doc.roles.violet = { definition: "chief-of-staff", identity: { name: "Violet Dispatch", email: "[email protected]", gitea: { url: "https://git.example", user: "violet" } } };
|
||||
doc.roles.opal = { definition: "chief-information-officer" };
|
||||
const records = [], prompts = [];
|
||||
for (const coordinator of ["violet", "opal"]) {
|
||||
const other = coordinator === "violet" ? "opal" : "violet";
|
||||
doc.roles[coordinator].definition = "chief-of-staff";
|
||||
doc.roles[other].definition = "chief-information-officer";
|
||||
doc.launch.by = coordinator;
|
||||
const business = validateBusinessDocument(doc, join(root, "businesses", "acme.json"), { rolesDir: REPO_ROLES });
|
||||
const resolve = (instance) => resolveInstance({ system: systemFor(root), business, instance });
|
||||
const resolved = resolve("assistant");
|
||||
const expectedName = coordinator === "violet" ? "Violet Dispatch" : null;
|
||||
assert.deepEqual(resolved.coordinator, { instance: coordinator, name: expectedName });
|
||||
assert.ok(Object.isFrozen(resolved.coordinator));
|
||||
assert.equal(resolved.launch, null, "routing context grants no launch authority");
|
||||
records.push(resolved);
|
||||
|
||||
const workspace = join(root, coordinator); mkdirSync(workspace);
|
||||
const bundle = buildBundle({ dir: join(workspace, "bundle"), resolved,
|
||||
contract: readFileSync(join(REPO_ROLES, "assistant.md"), "utf8"), harness: HARNESS.pi,
|
||||
session: { run: "assistant-run", launchedBy: business.human, workspace,
|
||||
toolSocket: join(workspace, "tools.sock"), turnMarker: join(workspace, "turn.done"),
|
||||
coordinator: { instance: other } } }); // Untrusted/session input cannot override the config.
|
||||
const prompt = readFileSync(bundle.files.prompt, "utf8");
|
||||
prompts.push(prompt);
|
||||
assert.match(prompt, /Launched by: jason\./);
|
||||
assert.ok(prompt.includes(`Configured coordinator: instance key "${coordinator}"`));
|
||||
assert.ok(!prompt.includes(`Configured coordinator: instance key "${other}"`));
|
||||
if (expectedName) assert.ok(prompt.includes(`configured identity name: "${expectedName}"`));
|
||||
assert.equal(bundle.manifest.resolvedDigest, resolved.digest);
|
||||
assert.equal(bundle.manifest.files["prompt.md"], sha(bundle.files.prompt));
|
||||
|
||||
const storeDir = join(workspace, "store"); mkdirSync(storeDir, { mode: 0o700 });
|
||||
const store = new Store(storeDir);
|
||||
t.after(() => store.close());
|
||||
const broker = new Broker({ store, businesses: { acme: { ...business,
|
||||
roles: Object.fromEntries(Object.entries(business.roles).map(([key, entry]) =>
|
||||
[key, { ...entry, authority: resolve(key).limits.authority }])) } } });
|
||||
const owner = broker.bindHuman({ business: "acme", human: business.human, via: "cli", outsideAgent: true });
|
||||
const call = (cap, verb, args = {}) => broker.request(cap, { verb, args });
|
||||
const project = call(owner, "project.create", { name: "Work" }).id;
|
||||
const thread = call(owner, "thread.create", { project, name: "Origin" }).id;
|
||||
const assistant = broker.bindLaunch({ business: "acme", role: "assistant", run: "assistant-run", harness: "pi", thread });
|
||||
call(assistant, "role.claim");
|
||||
assert.deepEqual(call(assistant, "agents"), [], "no coordinator or other non-thread role is running");
|
||||
assert.equal(store.get("SELECT count(*) n FROM role_claims WHERE role=?", coordinator).n, 0, "the coordinator has never claimed a run");
|
||||
const source = call(owner, "thread.send", { thread, body: "Build a timer" });
|
||||
const sent = call(assistant, "message.send", { to: resolved.coordinator.instance, body: "Build a timer", in_reply_to: source.id });
|
||||
assert.equal(store.get("SELECT to_role FROM messages WHERE id=?", sent.id).to_role, coordinator);
|
||||
assert.equal(sent.request, source.request);
|
||||
assert.throws(() => call(assistant, "message.send", { to: other, body: "Bypass", in_reply_to: source.id }), { code: "message-recipient-refused" });
|
||||
assert.throws(() => call(assistant, "message.send", { to: "chief-of-staff", body: "Guess" }), { code: "unknown-role" });
|
||||
}
|
||||
assert.notDeepEqual(records[0], records[1]);
|
||||
assert.notEqual(records[0].digest, records[1].digest);
|
||||
assert.notEqual(prompts[0], prompts[1]);
|
||||
});
|
||||
|
||||
test("an assistant without a configured launcher reports missing coordinator context", (t) => {
|
||||
const resolved = resolvedFor(t, "assistant", (doc) => { delete doc.launch; doc.roles.assistant = { definition: "assistant" }; });
|
||||
assert.equal(resolved.coordinator, null);
|
||||
const b = build(t, resolved);
|
||||
assert.match(readFileSync(b.files.prompt, "utf8"), /Configured coordinator: none\. Report the missing business launcher configuration/);
|
||||
});
|
||||
|
||||
function build(t, resolved, extra = {}) {
|
||||
const dir = scratch(t);
|
||||
const workspace = join(dir, "ws");
|
||||
|
||||
+5
-3
@@ -18,9 +18,11 @@ sessions.
|
||||
- Talk with the user. Answer what they ask, say plainly what you don't
|
||||
know and keep their requests in their own words.
|
||||
- Send each request for the business to the chief of staff as a message,
|
||||
addressed to the business's configured chief of staff instance, using
|
||||
its instance key or name. Keep the user's words as they gave them, and
|
||||
name the request each message carries.
|
||||
addressed to the exact instance key in the generated `Configured coordinator`
|
||||
line under `This session`. Use that key as send_message.to for requests
|
||||
and rulings, even when the chief is not running. If no coordinator is
|
||||
configured, report that blocker to the user. Keep the user's words as they
|
||||
gave them, and name the request each message carries.
|
||||
- Relay each ruling the user gives verbatim to the chief of staff,
|
||||
naming whose ruling it is and which role it concerns. Don't summarize
|
||||
it, paraphrase it or add to it. Don't send it to that role yourself.
|
||||
|
||||
Reference in new issue
Block a user