Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/bus/tests/citation-runtime.test.mjs
diff --git a/packages/bus/tests/citation-runtime.test.mjs b/packages/bus/tests/citation-runtime.test.mjs
new file mode 100644
index 000000000..b1a2de155
--- /dev/null
+++ b/packages/bus/tests/citation-runtime.test.mjs
@@ -0,0 +1,34 @@
+// E7: exercise startBroker's real live readRaw adapter, rather than replacing the library adapter.
~
+import test from 'node:test';
~
+import assert from 'node:assert/strict';
~
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
~
+import { tmpdir } from 'node:os';
~
+import { join } from 'node:path';
~
+import { startBroker } from '../src/runtime.mjs';
~
+import { validateBusinessDocument } from '../../business/src/business.mjs';
~
+import { businessDoc, rolesCopy, systemFor, writeJson } from '../../business/tests/helpers.mjs';
~
+import { mappingEntry } from '../../library/tests/helpers.mjs';
~
~
+test('C2-06 runtime reads changed and unreadable reply limits without restarting', async t => {
~
+  const root = mkdtempSync(join(tmpdir(), 'citation-runtime-')); t.after(() => rmSync(root, { recursive: true, force: true }));
~
+  const doc = businessDoc(root), rolesDir = rolesCopy(root), dir = join(root, 'config'), dataRoot = join(root, 'data');
~
+  doc.projects.books = mappingEntry(); doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } }; doc.replyLimits = { requireCitation: false, citationsPerReply: 3 };
~
+  const file = writeJson(join(dir, 'businesses/acme.json'), doc);
~
+  const checked = validateBusinessDocument(doc, file, { rolesDir }); mkdirSync(dataRoot, { mode: 0o700 });
~
+  // Runtime takes the host's broker projection, not the business validator's unresolved role entries.
~
+  // No agent action is needed here: use the same empty-authority shape as limits-runtime.test.mjs.
~
+  const business = { id: 'acme', human: 'jason', projects: checked.projects,
~
+    arbiters: { technical: 'coder', delivery: 'coder' },
~
+    roles: { coder: { definition: 'coder', authority: { withinRole: [], crossRole: [] } } } };
~
+  const runtime = await startBroker({ dataRoot, businesses: { acme: business }, organization: { dir, rolesDir, system: systemFor(root) } });
~
+  t.after(() => runtime.close());
~
+  const human = runtime.broker.bindHuman({ business: 'acme', human: 'jason', via: 'cli', outsideAgent: true });
~
+  const status = () => runtime.broker.request(human, { verb: 'library.status', args: { project: 'books' } });
~
+  assert.deepEqual((await status()).replyLimits, { requireCitation: false, citationsPerReply: 3, fallback: [], problem: null });
~
+  doc.replyLimits = { requireCitation: true, citationsPerReply: 7 }; writeJson(file, doc);
~
+  assert.deepEqual((await status()).replyLimits, { requireCitation: true, citationsPerReply: 7, fallback: [], problem: null });
~
+  writeFileSync(file, '{broken');
~
+  assert.deepEqual((await status()).replyLimits, { requireCitation: true, citationsPerReply: 20, fallback: [], problem: 'reply-limits-unreadable' });
~
+  rmSync(file);
~
+  assert.equal((await status()).replyLimits.problem, 'reply-limits-unreadable');
~
+});
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/bus/tests/creative.test.mjs
diff --git a/packages/bus/tests/creative.test.mjs b/packages/bus/tests/creative.test.mjs
index 6d421ba19..92365a8e8 100644
--- a/packages/bus/tests/creative.test.mjs
+++ b/packages/bus/tests/creative.test.mjs
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'node:url';
 import { Store } from '../src/store.mjs';
~
 import { Broker } from '../src/broker.mjs';
~
 import { startBroker } from '../src/runtime.mjs';
~
 import { businessDoc, 
+FAR_DATE,
  rolesCopy, systemFor, writeJson } from '../../business/tests/helpers.mjs';
~
 
~
 // Row 213 (C2-08, #1714, Case 2 design 5.1): a creative instance talks with the user only, and it has
~
 // no decision channel (M6). The messaging lists are read from the shipped role files.
~
@@ -98,7 +98,7 @@ async function runtime(t) {
   const rolesDir = rolesCopy(root);
~
   const doc = businessDoc(root, 'demo');
~
   doc.plugins = { ...doc.plugins, harness: ['pi'] };
~
   doc.roles.bean = { definition: 
-'companion'
+'companion', credentials: { gitea: { store: true, rotateBy: FAR_DATE } }
  };
~
   for (const r of Object.values(doc.roles)) r.vars = { harness: 'pi', model: 'claude-sonnet-5-5', thinking: 'high', ...r.vars };
~
   // Only bean names the sonnet key, so the chief may still change every other key.
~
   for (const [name, r] of Object.entries(doc.roles)) if (name !== 'bean') r.vars.model = 'claude-opus-5-5';
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/bus/tests/gitea-grant.test.mjs
diff --git a/packages/bus/tests/gitea-grant.test.mjs b/packages/bus/tests/gitea-grant.test.mjs
index f7c49986f..4934a3c4e 100644
--- a/packages/bus/tests/gitea-grant.test.mjs
+++ b/packages/bus/tests/gitea-grant.test.mjs
@@ -256,6 +256,22 @@ test('X-G1: a creative role, a role without identity and a missing or due slot r
   for (const f of [creative, bare, noSlot, due, elsewhere]) assert.equal(f.gitea.requests.length, 0);
~
 });
~
 
~
+// Row 285 (#1790, sage-2921): the chief's definition declares gitea for a read grant only, and this path
~
+// mints SEAT_SCOPES, so a chief with identity and publish still refuses at raise and stores nothing.
~
+test('X-G1: the chief of staff refuses at raise, whatever its definition, identity and publish declare', async (t) => {
~
+  const f = await setup(t, {
~
+    business: (b) => {
~
+      b.definitions['chief-of-staff'].credentials = [{ service: 'gitea' }];
~
+      b.roles.chief.identity = { name: 'Chief', email: 'chief@example.test', gitea: { url: b.roles.coder.identity.gitea.url, user: 'demo-chief' } };
~
+      b.roles.chief.publish = ['app'];
~
+    },
~
+  });
~
+  const n = f.store.get('SELECT count(*) AS n FROM decisions').n;
~
+  await rejects(f.raise({ agent: 'chief' }), 'gitea-not-declared');
~
+  assert.equal(f.store.get('SELECT count(*) AS n FROM decisions').n, n);
~
+  assert.equal(f.gitea.requests.length, 0);
~
+});
~
~
 test('X-G2: admin set refuses each failed check and writes nothing, then writes the slot once', async (t) => {
~
   const f = await setup(t, { slot: false });
~
   const set = (token, over = {}) =>
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/bus/tests/reload.test.mjs
diff --git a/packages/bus/tests/reload.test.mjs b/packages/bus/tests/reload.test.mjs
index 091b37b26..342d578d0 100644
--- a/packages/bus/tests/reload.test.mjs
+++ b/packages/bus/tests/reload.test.mjs
@@ -194,7 +194,7 @@ test('T14 a file whose project root differs from the boot copy is not reloaded,
 });
~
 
~
 test('T14 a file whose library mapping differs from the boot copy is not reloaded, because the mirror is ensured at start', async (t) => {
~
   const f = await setup(t, doc => { 
+doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
  doc.projects.books = mappingEntry(); });
~
   const doc = JSON.parse(readFileSync(f.file, 'utf8'));
~
   doc.projects.books.branch = 'other';
~
   writeFileSync(f.file, JSON.stringify(doc, null, 2) + '\n', { mode: 0o600 });
~
@@ -202,7 +202,7 @@ test('T14 a file whose library mapping differs from the boot copy is not reloade
   assert.equal(added.restart, true);
~
   assert.equal(f.reloads.length, 0);
~
   // The same library entry reloads.
~
   const g = await setup(t, doc => { 
+doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
  doc.projects.books = mappingEntry(); });
~
   assert.equal(g.ask('instances.set', { revision: g.revision(), instance: 'sec-1', op: 'add', definition: 'security-reviewer' }).restart, false);
~
   assert.equal(g.reloads.length, 1);
~
 });
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/business/tests/agent-identity.test.mjs
diff --git a/packages/business/tests/agent-identity.test.mjs b/packages/business/tests/agent-identity.test.mjs
index 3ea5876e0..8846b4e60 100644
--- a/packages/business/tests/agent-identity.test.mjs
+++ b/packages/business/tests/agent-identity.test.mjs
@@ -46,8 +46,28 @@ test("a granted agent loads with its identity, grant and the project's upstream"
 
~
 test("N10: publish needs identity and a gitea definition, and the error names the grant", () => {
~
   refuses(() => check((d) => { delete d.roles.coder.identity; }), /roles\.coder\.publish needs identity and a gitea definition/);
~
   
+// Row 285 (mos-1333 item 3): the chief's definition now declares gitea, so this case names an
~
+  // instance whose definition has none.
~
   refuses(() => check((d) => { 
-d.roles.chief.identity = structuredClone(IDENTITY); d.roles.chief.publish
+d.roles.helper
  = 
-["stack"];
+{ definition: "assistant", holder: "mos", identity: structuredClone(IDENTITY), publish: ["stack"] };
  }),
~
     
-/roles\.chief\.publish
+/roles\.helper\.publish
  needs identity and a gitea definition/);
~
+});
~
~
+// Row 285 (mos-1333 item 4, brief :1829): the chief's grant needs identity and publish on the
~
+// instance, so a chief with its gitea reference loads with both. X-G1 (gitea-grant.test) shows the
~
+// same chief refuses at raise.
~
+test("row 285: a chief with its gitea reference, identity and publish loads", async () => {
~
+  // Amendment 1 (mos-1580, sage-4060): this read reference belongs to a library business.
~
+  const { mappingEntry } = await import("../../library/tests/helpers.mjs");
~
+  const identity = { name: "Chief", email: "chief@example.com", gitea: { url: "https://git.example", user: "acme-chief" } };
~
+  const business = check((d) => {
~
+    d.projects.books = mappingEntry();
~
+    d.roles.chief.credentials = { gitea: { store: true, rotateBy: FAR_DATE } };
~
+    d.roles.chief.identity = structuredClone(identity);
~
+    d.roles.chief.publish = ["stack"];
~
+  });
~
+  assert.deepEqual({ ...business.roles.chief.identity, gitea: { ...business.roles.chief.identity.gitea } }, identity);
~
+  assert.deepEqual([...business.roles.chief.publish], ["stack"]);
~
+  assert.deepEqual(Object.keys(business.roles.chief.credentials), ["gitea"]);
~
 });
~
 
~
 test("publish names declared projects that have a remote, each once", () => {
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/business/tests/business.test.mjs
diff --git a/packages/business/tests/business.test.mjs b/packages/business/tests/business.test.mjs
index 9d4eda831..f78789250 100644
--- a/packages/business/tests/business.test.mjs
+++ b/packages/business/tests/business.test.mjs
@@ -6,6 +6,7 @@ import { join } from "node:path";
 import { pathToFileURL } from "node:url";
~
 import { BusinessError, businessFilePath, configDir, loadBusiness, validateBusinessDocument } from "../src/index.mjs";
~
 import { THREAD_IDLE_SECONDS } from "../src/business.mjs";
~
+import { CREATIVE_DEFINITIONS } from "../src/vocabulary.mjs";
~
 import { launchDoc, businessDoc, REPO, REPO_ROLES, rolesCopy, tmp, writeJson } from "./helpers.mjs";
~
 
~
 test('C2-04: library mappings load without roots, remain frozen, and retain mapping refusals', async t => {
~
@@ -14,6 +15,7 @@ test('C2-04: library mappings load without roots, remain frozen, and retain mapp
   const root = tmp(); t.after(() => rmSync(root, { recursive: true, force: true }));
~
   const doc = businessDoc(root), file = join(root, 'businesses/acme.json');
~
   doc.projects = { books: mappingEntry(), ...doc.projects };
~
   
+doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
   const loaded = validateBusinessDocument(doc, file, { rolesDir: REPO_ROLES });
~
   const { checkMapping } = await import('../../library/src/mapping.mjs');
~
   assert.deepEqual(loaded.projects.books, checkMapping(mappingEntry()));
~
@@ -41,6 +43,7 @@ test('C2-04: an otherwise valid publish grant cannot name a library repo', async
   const root = tmp(); t.after(() => rmSync(root, { recursive: true, force: true }));
~
   const doc = businessDoc(root);
~
   doc.projects.books = { ...mappingEntry(), remote: 'https://git.example/writer/books' };
~
   
+doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
   doc.roles.coder.identity = { name: 'Coder', email: 'coder@example.invalid', gitea: { url: 'https://git.example', user: 'acme-coder' } };
~
   doc.roles.coder.publish = ['books'];
~
   refuses(() => validateBusinessDocument(doc, join(root, 'businesses/acme.json'), { rolesDir: REPO_ROLES }), /publish.*library/);
~
@@ -273,28 +276,58 @@ test("the shipped example refuses as written and validates once filled in", () =
   assert.equal(id, 15);
~
 });
~
 
~
+// Row 285 (C2-08b, #1790, mos-1330): a creative definition declares gitea, so its instance names
~
+// the reference. The store form needs no token file, and the bus reads a missing one as no token.
~
+const CREATIVE_GITEA = Object.freeze({ gitea: Object.freeze({ store: true, rotateBy: "2099-01-01" }) });
~
~
 // Row 213 (C2-08, #1714), deviation D1: the persona vars sit on a creative instance only, and
~
 // both or neither. The var checks themselves are vars.mjs's.
~
 test("persona vars: agent layer, creative definition only, both or neither", () => {
~
   const sha = "a".repeat(64);
~
   const persona = { "persona.name": "Bean", "persona.sha256": sha };
~
   const business = check((d) => { d.roles.bean = { definition: "companion", 
+credentials: CREATIVE_GITEA,
  vars: { ...persona } }; });
~
   assert.deepEqual({ ...business.roles.bean.vars }, persona);
~
   assert.doesNotThrow(() => check((d) => { d.roles.bean = { definition: 
-"ideas"
+"ideas", credentials: CREATIVE_GITEA
  }; }));
~
   refuses(() => check((d) => { d.vars["persona.name"] = "Bean"; }), /persona\.name can't be set at the business layer/);
~
   refuses(() => check((d) => { d.roles.pm.vars = { ...d.roles.pm.vars, ...persona }; }),
~
     /roles\.pm\.vars: persona vars need a creative definition \(pm isn't one\)/);
~
   refuses(() => check((d) => { d.roles.chief.vars = { ...persona }; }), /chief-of-staff isn't one/);
~
   for (const key of Object.keys(persona)) {
~
     refuses(() => check((d) => { d.roles.bean = { definition: "research", 
+credentials: CREATIVE_GITEA,
  vars: { [key]: persona[key] } }; }),
~
       /roles\.bean\.vars: persona\.name and persona\.sha256 are set together or not at all/);
~
   }
~
   for (const [key, value] of [["persona.name", "x".repeat(41)], ["persona.name", "Be\nan"], ["persona.name", "Be\u0007an"], ["persona.name", "Be\u2028an"], ["persona.name", "Be\u2029an"], ["persona.name", 7],
~
     ["persona.sha256", "A".repeat(64)], ["persona.sha256", sha.slice(1)], ["persona.sha256", `${sha}0`]]) {
~
     refuses(() => check((d) => { d.roles.bean = { definition: "companion", 
+credentials: CREATIVE_GITEA,
  vars: { ...persona, [key]: value } }; }), new RegExp(`variable ${key.replace(".", "\\.")}`), 2);
~
   
+}
~
+});
~
~
+// Row 285 (C2-08b, #1790, mos-1330): the chief and each creative definition declare gitea, so an
~
+// instance without the reference refuses. Amendment 1 (mos-1580, sage-4060): the chief
~
+// requires its reference only in a library business; creative definitions always require it.
~
+test("row 285: a chief or creative instance without credentials.gitea refuses at load", async () => {
~
+  const { mappingEntry } = await import("../../library/tests/helpers.mjs");
~
+  refuses(() => check((d) => { d.projects.books = mappingEntry(); delete d.roles.chief.credentials; }),
~
+    /chief-library-gitea-required: .*roles\.chief\.credentials/);
~
+  for (const definition of CREATIVE_DEFINITIONS) {
~
+    refuses(() => check((d) => { d.roles.bean = { definition }; }),
~
+      /roles\.bean\.credentials must reference exactly the services its role definition needs \(gitea; got none\)/);
~
+    assert.doesNotThrow(() => check((d) => { d.roles.bean = { definition, credentials: CREATIVE_GITEA }; }), definition);
~
   }
~
 });
~
 
~
+// Row 285 (C2-08b, brief): a creative instance may carry identity and publish, under the rules at
~
+// business.mjs:155-166, which the grant path reads. The grant itself still refuses it (#giteaRole).
~
+test("row 285: a creative instance with identity and publish loads", () => {
~
+  const identity = { name: "Bean", email: "bean@example.test", gitea: { url: "https://git.example", user: "acme-bean" } };
~
+  const business = check((d) => {
~
+    d.projects.stack.remote = "https://git.example/acme/stack";
~
+    d.roles.bean = { definition: "companion", credentials: CREATIVE_GITEA, identity, publish: ["stack"] };
~
+  });
~
+  assert.deepEqual({ ...business.roles.bean.identity, gitea: { ...business.roles.bean.identity.gitea } }, identity);
~
+  assert.deepEqual([...business.roles.bean.publish], ["stack"]);
~
+});
~
~
 // Row 194 (#1692, D2.5, T17): the per-agent forbid and the retired list.
~
 test("row 194 keep, noHire and retired validate, and a file without them loads as before", () => {
~
   const plain = check();
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/business/tests/chief-library-credentials.test.mjs
diff --git a/packages/business/tests/chief-library-credentials.test.mjs b/packages/business/tests/chief-library-credentials.test.mjs
new file mode 100644
index 000000000..016544b46
--- /dev/null
+++ b/packages/business/tests/chief-library-credentials.test.mjs
@@ -0,0 +1,53 @@
+// Row 285 amendment 1 (#1790, mos-1580, sage-4059): a chief references gitea
~
+// exactly when this business declares a library. The Case 1 source stays unchanged.
~
+import { test } from 'node:test';
~
+import assert from 'node:assert/strict';
~
+import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
~
+import { tmpdir } from 'node:os';
~
+import { join } from 'node:path';
~
+import { fileURLToPath } from 'node:url';
~
+import { BusinessError, validateBusinessDocument } from '../src/index.mjs';
~
+import { mappingEntry } from '../../library/tests/helpers.mjs';
~
~
+const ROLES = fileURLToPath(new URL('../../../roles/', import.meta.url));
~
+const CASE1 = new URL('../../../docs/guides/case1-business.mosaic-stack.json', import.meta.url);
~
+const GITEA = { gitea: { store: true, rotateBy: '2099-01-01' } };
~
~
+function fixture(t) {
~
+  const root = mkdtempSync(join(tmpdir(), 'chief-library-'));
~
+  t.after(() => rmSync(root, { recursive: true, force: true }));
~
+  const doc = JSON.parse(readFileSync(CASE1, 'utf8'));
~
+  const validate = () => validateBusinessDocument(doc, join(root, 'businesses/mosaic-stack.json'), { rolesDir: ROLES });
~
+  return { doc, validate };
~
+}
~
~
+// Removing the library condition makes this successful Case 1 load fail.
~
+test('row 285 amendment 1: no library loads with empty chief credentials', t => {
~
+  const { doc, validate } = fixture(t);
~
+  doc.roles.chief.credentials = {};
~
+  const business = validate();
~
+  assert.deepEqual(business.roles.chief.credentials, {});
~
+  assert.deepEqual(business.roles.assistant.credentials, {});
~
+});
~
~
+// Dropping the required-reference refusal makes the missing-reference assertion fail.
~
+test('row 285 amendment 1: a library without chief gitea refuses by name', t => {
~
+  const { doc, validate } = fixture(t);
~
+  doc.projects.books = mappingEntry();
~
+  assert.throws(validate, error => error instanceof BusinessError && error.exitCode === 2 &&
~
+    /chief-library-gitea-required: .*roles\.chief\.credentials/.test(error.message));
~
+  doc.roles.chief.credentials = structuredClone(GITEA);
~
+  assert.deepEqual(Object.keys(validate().roles.chief.credentials), ['gitea']);
~
+});
~
~
+// Letting the chief keep unused gitea, or weakening exact-match globally, breaks this refusal.
~
+test('row 285 amendment 1: no library with chief gitea refuses the extra reference', t => {
~
+  const { doc, validate } = fixture(t);
~
+  doc.roles.chief.credentials = structuredClone(GITEA);
~
+  assert.throws(validate, error => error instanceof BusinessError && error.exitCode === 2 &&
~
+    /roles\.chief\.credentials must reference exactly the services its role definition needs \(none; got gitea\)/.test(error.message));
~
+  delete doc.roles.chief.credentials;
~
+  doc.roles.assistant.credentials = structuredClone(GITEA);
~
+  assert.throws(validate, error => error instanceof BusinessError && error.exitCode === 2 &&
~
+    /roles\.assistant\.credentials must reference exactly the services its role definition needs \(none; got gitea\)/.test(error.message));
~
+});
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/business/tests/chief-of-staff.test.mjs
diff --git a/packages/business/tests/chief-of-staff.test.mjs b/packages/business/tests/chief-of-staff.test.mjs
index 35b9b8505..9d772a54f 100644
--- a/packages/business/tests/chief-of-staff.test.mjs
+++ b/packages/business/tests/chief-of-staff.test.mjs
@@ -20,7 +20,8 @@ const APPROVED = Object.freeze({
   tools: ["read", "grep", "find", "ls"],
~
   network: "api-only",
~
   authority: Object.freeze({ withinRole: Object.freeze(["message.send", "role.launch", "role.end", "organization.change", "record.write", "instance.name", "role.hire", "role.fire"]), crossRole: Object.freeze([]) }),
~
   
+// Row 285 (#1790, mos-1309 item 5, sage-2915): gitea for the library read grant, read scopes only.
~
   credentials: 
-Object.freeze([]),
+Object.freeze([Object.freeze({ service: "gitea", scopes: Object.freeze(["read:repository", "read:user"]) })]),
~
   // Row 194 (#1692, Q4, sage-1374): empty until a policy commit after the PRD 1.7 resolve.
~
   hireable: Object.freeze([]),
~
 });
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/business/tests/cli.test.mjs
diff --git a/packages/business/tests/cli.test.mjs b/packages/business/tests/cli.test.mjs
index 3aa1d52ee..beb367f8c 100644
--- a/packages/business/tests/cli.test.mjs
+++ b/packages/business/tests/cli.test.mjs
@@ -11,7 +11,7 @@ const CLI = join(REPO, "packages", "business", "src", "cli.mjs");
 test('C2-04: business validate and resolve skip a library before code projects and credential checks', async t => {
~
   const { mappingEntry } = await import('../../library/tests/helpers.mjs');
~
   const { rmSync } = await import('node:fs');
~
   const s = setup(doc => { 
+doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
  doc.projects = { books: mappingEntry(), ...doc.projects }; });
~
   t.after(() => rmSync(s.root, { recursive: true, force: true }));
~
   writeJson(projectFilePath(s.doc.projects.stack.root), { projectVersion: 1, id: 'stack', vars: { 'tracker.project': 3 } });
~
   const checked = run(s, 'validate', 'acme');
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/business/tests/hard-waits.test.mjs
diff --git a/packages/business/tests/hard-waits.test.mjs b/packages/business/tests/hard-waits.test.mjs
index 7881c93c5..9ff377176 100644
--- a/packages/business/tests/hard-waits.test.mjs
+++ b/packages/business/tests/hard-waits.test.mjs
@@ -38,6 +38,13 @@ test("REQ-COS-3: role.hire and role.fire are routed, not hard waits", () => {
   for (const action of ["role.hire", "role.fire"]) assert.ok(ROUTED.includes(action) && !GATED_ONLY.includes(action), action);
~
 });
~
 
~
+// PRD 1.16 REQ-WAIT-3 and REQ-LIB-6 (row 285, #1790): library.write is in the vocabulary and routed.
~
+test("REQ-WAIT-3: library.write is routed under REQ-LIB-6 (1.16), not a hard wait", () => {
~
+  assert.ok(ACTIONS.includes("library.write"));
~
+  assert.ok(ROUTED.includes("library.write"));
~
+  assert.ok(!GATED_ONLY.includes("library.write"));
~
+});
~
~
 test("REQ-WAIT-3: every action is routed or a hard wait, never both, and the routed list names only actions", () => {
~
   for (const action of ROUTED) assert.ok(ACTIONS.includes(action), action);
~
   for (const action of ACTIONS) assert.notEqual(ROUTED.includes(action), GATED_ONLY.includes(action), action);
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/business/tests/instance-credential-transitions.test.mjs
diff --git a/packages/business/tests/instance-credential-transitions.test.mjs b/packages/business/tests/instance-credential-transitions.test.mjs
new file mode 100644
index 000000000..d72f86778
--- /dev/null
+++ b/packages/business/tests/instance-credential-transitions.test.mjs
@@ -0,0 +1,70 @@
+// 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);
~
+});
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/business/tests/instance-settings.test.mjs
diff --git a/packages/business/tests/instance-settings.test.mjs b/packages/business/tests/instance-settings.test.mjs
index e85366562..54c22e0e0 100644
--- a/packages/business/tests/instance-settings.test.mjs
+++ b/packages/business/tests/instance-settings.test.mjs
@@ -9,7 +9,7 @@ import { mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync }
 import { join } from "node:path";
~
 import { instanceSettings } from "../src/instance-settings.mjs";
~
 import { projectFilePath } from "../src/project.mjs";
~
 import { businessDoc, 
+FAR_DATE,
  REPO, rolesCopy, systemConfig, systemFor, tmp, writeJson } from "./helpers.mjs";
~
 
~
 const CLI = join(REPO, "packages", "business", "src", "cli.mjs");
~
 const roots = [];
~
@@ -22,7 +22,7 @@ const LEVELS = { pi: ["off", "minimal", "low", "medium", "high", "xhigh", "max"]
 
~
 test('C2-04: instance settings skip a library while checking a later code project', async () => {
~
   const { mappingEntry } = await import('../../library/tests/helpers.mjs');
~
   const f = fixture(doc => { 
+doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
  doc.projects = { books: mappingEntry(), ...doc.projects }; });
~
   writeJson(projectFilePath(f.doc.projects.stack.root), { projectVersion: 1, id: 'stack', vars: { 'tracker.project': 3 } });
~
   const before = f.settings.read('acme');
~
   f.settings.set('acme', { revision: before.revision, instance: 'gate-215', op: 'add', definition: 'merge-gate' }, 'jason');
~
@@ -286,7 +286,8 @@ test("row 213 a creative instance is the human's: add, vars and definition chang
   writeFileSync(join(f.root, "config", "personas", "acme", "bean.md"), text, { mode: 0o600 });
~
   const persona = { "persona.name": "Bean", "persona.sha256": createHash("sha256").update(text).digest("hex") };
~
   const view = f.settings.read("acme");
~
   
+// Row 285 (mos-1332 item 4): the companion definition declares gitea.
~
   assert.deepEqual(view.definitions.companion, { services: 
-[],
+["gitea"],
  accepted: true });
~
   const add = { instance: "bean", op: "add", definition: "companion", vars: persona };
~
   for (const actor of ["chief-of-staff.chief", "tester"]) {
~
     assert.throws(() => f.settings.set("acme", { revision: view.revision, ...add }, actor), e => e.code === "organization-creative-human-only", actor);
~
@@ -331,8 +332,8 @@ test("row 213 a creative instance is the human's: add, vars and definition chang
 // every actor, the human included. The listing's endProtected is that same check for its reader.
~
 test("row 256 T1-T3, T5 the human deletes a creative instance, no other actor does, and the assistant stays", () => {
~
   const f = fixture(doc => {
~
     doc.roles.bean = { definition: 
-"companion"
+"companion", credentials: { gitea: { store: true, rotateBy: FAR_DATE } }
  };
~
     doc.roles.muse = { definition: 
-"ideas"
+"ideas", credentials: { gitea: { store: true, rotateBy: FAR_DATE } }
  };
~
     doc.roles.assistant = { definition: "assistant", holder: "mos" };
~
   });
~
   const del = (instance, actor) => () => f.settings.set("acme", { revision: f.settings.read("acme").revision, instance, op: "delete" }, actor);
~
@@ -377,7 +378,7 @@ test("row 256 T1-T3, T5 the human deletes a creative instance, no other actor do
 // instance can't be kept, launch.by or an arbiter, so the check it reaches is the last-listed one.
~
 test("row 256 T4 the human's delete of the only listed instance, a creative one, is the last-listed refusal", () => {
~
   const f = fixture(doc => {
~
     doc.roles.bean = { definition: 
-"companion"
+"companion", credentials: { gitea: { store: true, rotateBy: FAR_DATE } }
  };
~
     doc.launch.instances = ["bean"];
~
   });
~
   const bytes = readFileSync(f.file, "utf8");
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/business/tests/launch-settings.test.mjs
diff --git a/packages/business/tests/launch-settings.test.mjs b/packages/business/tests/launch-settings.test.mjs
index a1f789903..41c93400a 100644
--- a/packages/business/tests/launch-settings.test.mjs
+++ b/packages/business/tests/launch-settings.test.mjs
@@ -181,7 +181,7 @@ test('B7 kept instance: its route is the human\'s',async()=>{
 // can't reach its model through the models table, the instances key or a route; the human can,
~
 // except through the instances key, which refuses a creative instance for everyone.
~
 test('M10 creative instance: models keys it names, the instances key and its route are the human\'s',async()=>{
~
  const f=fixture();const 
-doc=structuredClone(f.doc);doc.roles.bean={definition:'companion',vars:{model:'sonnet'}};writeJson(f.file,doc);
+doc=structuredClone(f.doc);doc.roles.bean={definition:'companion',credentials:{gitea:{store:true,rotateBy:FAR_DATE}},vars:{model:'sonnet'}};writeJson(f.file,doc);
~
  const g={...f,doc};const {settings}=await routeSettings(g);const file=()=>readFileSync(f.file,'utf8');
~
  const chief='chief-of-staff.chief';let {revision}=settings.read('acme');
~
  const models=structuredClone(settings.read('acme').models);
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/business/tests/library-projects-protected.test.mjs
diff --git a/packages/business/tests/library-projects-protected.test.mjs b/packages/business/tests/library-projects-protected.test.mjs
index 51672bd7b..d4d09d94f 100644
--- a/packages/business/tests/library-projects-protected.test.mjs
+++ b/packages/business/tests/library-projects-protected.test.mjs
@@ -14,6 +14,7 @@ function fixture(t) {
   books.universes.hybrid = { kind: 'hybrid', name: 'An invented crossover',
~
     sources: ['harbor', 'second'], books: ['Hybrid Book'], playground: [], constraints: [], notes: 'Fixture only' };
~
   doc.projects = { books, ...doc.projects };
~
   
+doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
   const dir = join(root, 'config'), file = writeJson(join(dir, 'businesses/acme.json'), doc);
~
   const writer = organizationWriter({ dir, rolesDir: rolesCopy(root) });
~
   // Give both snapshots an existing ordinary committed receipt to preserve.
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/business/tests/library-seats.test.mjs
diff --git a/packages/business/tests/library-seats.test.mjs b/packages/business/tests/library-seats.test.mjs
index 3cd0f86cf..da76fdbc7 100644
--- a/packages/business/tests/library-seats.test.mjs
+++ b/packages/business/tests/library-seats.test.mjs
@@ -27,7 +27,9 @@ test("M7/M8: library seats come from library businesses, creative seats from the
   const { root, dir } = fixture(t);
~
   const doc = businessDoc(root, "acme");
~
   doc.projects = { books: mappingEntry(), ...doc.projects };
~
   
+doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
   doc.roles.bean = { definition: "companion" };
~
   
+doc.roles.bean.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
   writeJson(join(dir, "businesses", "acme.json"), doc);
~
   const found = sets(dir);
~
   assert.deepEqual([...found.librarySeats].sort(), ["bean", "chief", "coder", "cto", "pm", "reviewer"]);
~
@@ -39,6 +41,7 @@ test("M8: a business with no library project puts no seat in either set", (t) =>
   const { root, dir } = fixture(t);
~
   const doc = businessDoc(root, "other");
~
   doc.roles.bean = { definition: "companion" };
~
   
+doc.roles.bean.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
   writeJson(join(dir, "businesses", "other.json"), doc);
~
   const found = sets(dir);
~
   assert.equal(found.librarySeats.size, 0);
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/business/tests/lifecycle-settings.test.mjs
diff --git a/packages/business/tests/lifecycle-settings.test.mjs b/packages/business/tests/lifecycle-settings.test.mjs
index 3c872b4a5..2a80b4e04 100644
--- a/packages/business/tests/lifecycle-settings.test.mjs
+++ b/packages/business/tests/lifecycle-settings.test.mjs
@@ -9,7 +9,7 @@ import { join } from "node:path";
 import { instanceSettings } from "../src/instance-settings.mjs";
~
 import { OrganizationError, organizationWriter } from "../src/organization.mjs";
~
 import { CREATIVE_DEFINITIONS } from "../src/vocabulary.mjs";
~
 import { businessDoc, 
+FAR_DATE,
  REPO, rolesCopy, systemConfig, systemFor, tmp, writeJson } from "./helpers.mjs";
~
 
~
 const CLI = join(REPO, "packages", "business", "src", "cli.mjs");
~
 const roots = [];
~
@@ -235,7 +235,7 @@ test("D2.2 fire delists and keeps the entry, a repeat is done with no write, and
 // every actor's fire, the human's included.
~
 test("row 256 checkDelete passes the human's creative delete and refuses another actor's; fire still refuses", () => {
~
   const f = fixture(doc => {
~
     doc.roles.bean = { definition: 
-"companion"
+"companion", credentials: { gitea: { store: true, rotateBy: FAR_DATE } }
  };
~
     doc.launch.instances.push("bean");
~
   });
~
   const bytes = readFileSync(f.file, "utf8");
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/business/tests/resolve.test.mjs
diff --git a/packages/business/tests/resolve.test.mjs b/packages/business/tests/resolve.test.mjs
index b1e50f349..2fa106053 100644
--- a/packages/business/tests/resolve.test.mjs
+++ b/packages/business/tests/resolve.test.mjs
@@ -31,7 +31,7 @@ const resolve = (s, instance) => resolveInstance({ system: s.system, business: s
 test('C2-04: a library first in projects does not hide a later project layer', async t => {
~
   const { mappingEntry } = await import('../../library/tests/helpers.mjs');
~
   const { rmSync } = await import('node:fs');
~
   const s = setup(doc => { 
+doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
  doc.projects = { books: mappingEntry(), ...doc.projects }; }, { vars: { 'tracker.project': 7 } });
~
   t.after(() => rmSync(s.root, { recursive: true, force: true }));
~
   const result = resolve(s, 'coder');
~
   assert.equal(result.project, 'stack');
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/business/tests/role.test.mjs
diff --git a/packages/business/tests/role.test.mjs b/packages/business/tests/role.test.mjs
index d5d5eb5bd..690ef71d2 100644
--- a/packages/business/tests/role.test.mjs
+++ b/packages/business/tests/role.test.mjs
@@ -226,14 +226,16 @@ test("contract: a non-empty regular Markdown file beside the role file", () => {
 // Row 213 (C2-08, #1714, M1): a creative role reads and answers the human, and nothing else.
~
 const CREATIVE = { companion: ["read", "web"], ideas: ["read"], research: ["read", "web"], proofreader: ["read"], "fact-checker": ["read"], continuity: ["read"] };
~
 
~
+// Row 285 (C2-08b, #1790, mos-1309 item 5): each also holds library.write and the gitea credential, whose
~
+// scopes equal what the Gitea grant path mints (gitea-grant.mjs SEAT_SCOPES, sage-2915).
~
 test("the six creative roles load: read tools, message.send 
-only,
+and library.write, gitea,
  messaging with the human only", () => {
~
   for (const [name, tools] of Object.entries(CREATIVE)) {
~
     const role = loadRole(REPO_ROLES, name);
~
     assert.equal(role.roleVersion, 2, name);
~
     assert.deepEqual(role.tools, tools, name);
~
     assert.equal(role.network, tools.includes("web") ? "api-only" : "none", name);
~
     assert.deepEqual(role.authority, { withinRole: 
-["message.send"],
+["message.send", "library.write"],
  crossRole: [] }, name);
~
     assert.deepEqual(role.credentials, 
-[],
+[{ service: "gitea", scopes: ["read:user", "write:repository"] }],
  name);
~
     assert.deepEqual(role.messaging, { sendTo: ["human"], receiveFrom: ["human"] }, name);
~
   }
~
 });
~
@@ -255,8 +257,20 @@ test("M1: a creative role file wider than read and answer is refused", () => {
   for (const tool of ["write", "edit", "bash"]) {
~
     refuses(creative("ideas", { tools: ["read", tool] }), new RegExp(`^creative role ideas may not name tools ${tool} `));
~
   }
~
   
-refuses(creative("proofreader",
+const gitea = [{ service: "gitea", scopes: ["read:user", "write:repository"] }];
~
+  assert.doesNotThrow(creative("ideas",
  { authority: { withinRole: ["message.send", 
-"record.write"],
+"library.write"],
  crossRole: [] 
-} }),
+}, credentials: gitea }));
~
+  // Row 285: any other action, at either class, refuses, each routed action looped from the list.
~
+  const only =
  /^creative role proofreader may hold only withinRole 
-message.send$/);
+message\.send and library\.write$/;
~
+  for (const action of ACTIONS.filter((a) => !GATED_ONLY.includes(a) && !["message.send", "library.write"].includes(a))) {
~
+    refuses(creative("proofreader", { authority: { withinRole: ["message.send", "library.write", action], crossRole: [] } }), only);
~
+  }
~
   refuses(creative("proofreader", { authority: { withinRole: [], crossRole: ["message.send"] } }), 
+only);
~
+  refuses(creative("proofreader", { authority: { withinRole: ["message.send"], crossRole: ["library.write"] } }), only);
~
+  // Row 285: library.write with any credential but gitea refuses.
~
+  refuses(creative("ideas", { authority: { withinRole: ["message.send", "library.write"], crossRole: [] }, credentials: [...gitea, { service: "vikunja", scopes: { tasks: ["read_one"] } }] }),
~
     /^creative role 
-proofreader
+ideas
  may hold only 
-withinRole message.send$/);
+the gitea credential \(got vikunja\)$/);
~
+  refuses(creative("ideas", { authority: { withinRole: ["message.send", "library.write"], crossRole: [] }, credentials: [{ service: "host" }] }),
~
+    /^creative role ideas may hold only the gitea credential \(got host\)$/);
~
   refuses(creative("continuity", { messaging: undefined }), /^creative role continuity requires "messaging"$/);
~
   refuses(creative("research", { messaging: { sendTo: ["human", "pm"], receiveFrom: ["human"] } }), /^creative role research messaging.sendTo may name only human$/);
~
   refuses(creative("research", { messaging: { sendTo: ["human"], receiveFrom: ["assistant"] } }), /^creative role research messaging.receiveFrom may name only human$/);
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/cli/tests/case2-install.test.mjs
diff --git a/packages/cli/tests/case2-install.test.mjs b/packages/cli/tests/case2-install.test.mjs
index cc1142510..51b606bda 100644
--- a/packages/cli/tests/case2-install.test.mjs
+++ b/packages/cli/tests/case2-install.test.mjs
@@ -51,8 +51,11 @@ function world(t, { profile = "library" } = {}) {
   for (const r of Object.values(doc.roles)) r.vars = { ...r.vars, harness: "pi" };
~
   if (profile === "library") {
~
     doc.projects.books = { ...mappingEntry(), user: "writer" };
~
     
+doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
     doc.roles.bean = { definition: "companion", vars: { harness: "pi" } };
~
     
+doc.roles.bean.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
     doc.roles.chet = { definition: "companion", vars: { harness: "pi" } };
~
     
+doc.roles.chet.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
   } else doc.vars["bus.tester"] = true;
~
   const input = writeJson(join(root, "input", "acme.json"), doc);
~
   const staging = join(root, "staging");
~
@@ -212,6 +215,7 @@ test("the factory install refuses a library project and bus.tester off, before a
   const x = world(t, { profile: "factory" });
~
   const doc = JSON.parse(readFileSync(x.input, "utf8"));
~
   doc.projects.books = { ...mappingEntry(), user: "writer" };
~
   
+doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
   writeJson(x.input, doc);
~
   refused(install(x, { profile: "factory" }), /profile-library-business/);
~
   delete doc.projects.books;
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/cli/tests/launcher.test.mjs
diff --git a/packages/cli/tests/launcher.test.mjs b/packages/cli/tests/launcher.test.mjs
index baabd843e..766bbb35a 100644
--- a/packages/cli/tests/launcher.test.mjs
+++ b/packages/cli/tests/launcher.test.mjs
@@ -1,4 +1,4 @@
 import { 
+FAR_DATE,
  launchDoc } from "../../business/tests/helpers.mjs";
~
 // The S6 launcher inside a real bus host: real broker child, real runners
~
 // in the bwrap sandbox, the fake adapter from packages/harness standing in for the
~
 // harness. The chief of staff launches a coder through its `launch` tool (row 119: the
~
@@ -32,7 +32,7 @@ const skip = sandboxProblem() ?? false;
 
~
 test('C2-04: launcher workspace clones code but never the library', { skip, timeout: 30000 }, async t => {
~
   const { mappingEntry } = await import('../../library/tests/helpers.mjs');
~
   const f = await setup(t, doc => { 
+doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
  doc.projects = { books: mappingEntry(), ...doc.projects }; return doc; });
~
   await f.launcher.launchPm();
~
   const workspace = workspaceDir(f.f.dataRoot, 'acme', 'chief');
~
   assert.equal(existsSync(join(workspace, 'stack', '.git')), true);
~
@@ -766,7 +766,7 @@ test("row 64 a role skill that isn't enabled refuses skill-not-enabled, and one
 test("row 213 a creative instance's persona is read from <configDir>/personas: off its digest the launch fails with the mismatch as its reason", { timeout: 30000 }, async (t) => {
~
   const text = "Warm. PERSONA-MARKER-d2\n";
~
   const given = prepare(t, (doc) => {
~
     doc.roles.bean = { definition: "companion", 
+credentials: { gitea: { ...doc.roles.coder.credentials.gitea } },
  vars: { harness: "pi", model: "claude-sonnet-5-5", thinking: "high", "persona.name": "Bean", "persona.sha256": createHash("sha256").update(text).digest("hex") } };
~
     doc.launch.instances.push("bean");
~
     return doc;
~
   });
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/cli/tests/thread-launch.test.mjs
diff --git a/packages/cli/tests/thread-launch.test.mjs b/packages/cli/tests/thread-launch.test.mjs
index fa3adb866..3e313e49d 100644
--- a/packages/cli/tests/thread-launch.test.mjs
+++ b/packages/cli/tests/thread-launch.test.mjs
@@ -400,6 +400,7 @@ test("REQ-THREAD-1 REQ-LAUNCH-1 the chief can't launch the assistant outside a t
 test('C2-09a: fromSession refuses a creative instance outside a thread without a sandbox', { timeout: 30000 }, async t => {
~
   const prepared = prepare(t, { edit: doc => {
~
     doc.roles.bean = { ...doc.roles.assistant, definition: 'companion' };
~
     
+doc.roles.bean.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
     doc.launch.instances.push('bean');
~
     return doc;
~
   } });
~
@@ -465,7 +466,9 @@ test('C2-09a: creative thread messages start distinct runs and keep each instanc
     const vars = { ...doc.roles.assistant.vars };
~
     delete doc.roles.assistant;
~
     doc.roles.bean = { definition: 'companion', vars: { ...vars } };
~
     
+doc.roles.bean.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
     doc.roles.chet = { definition: 'continuity', vars: { ...vars } };
~
     
+doc.roles.chet.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
     doc.launch.instances = ['coder', 'bean', 'chet'];
~
     return doc;
~
   } });
~
@@ -501,6 +504,7 @@ test('C2-09a: creative thread messages start distinct runs and keep each instanc
 test('C2-09a: terminating one creative thread does not signal an unselected peer of the same instance', { skip, timeout: 90000 }, async t => {
~
   const prepared = prepare(t, { sonnet: 2, idle: 60, edit: doc => {
~
     doc.roles.bean = { ...doc.roles.assistant, definition: 'companion' };
~
     
+doc.roles.bean.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
     delete doc.roles.assistant;
~
     doc.launch.instances = ['coder', 'bean'];
~
     return doc;
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/cli/tests/trackers-boot.test.mjs
diff --git a/packages/cli/tests/trackers-boot.test.mjs b/packages/cli/tests/trackers-boot.test.mjs
index 8d1a407c6..e7f50c947 100644
--- a/packages/cli/tests/trackers-boot.test.mjs
+++ b/packages/cli/tests/trackers-boot.test.mjs
@@ -14,7 +14,7 @@ import { fixture, tmp } from "./helpers.mjs";
 
~
 test('C2-04: a library-only business with tracker.baseUrl boots without a project tracker', async t => {
~
   const { mappingEntry } = await import('../../library/tests/helpers.mjs');
~
   const f = fixture(tmp(t), 'acme', doc => { 
+doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
  doc.projects = { books: mappingEntry() }; return doc; });
~
   const warnings = [];
~
   const boot = bootConfig({ system: loadSystem({ env: f.env }), businessId: 'acme', env: f.env, warn: text => warnings.push(text) });
~
   assert.equal(boot.trackers, undefined);
~
@@ -40,6 +40,7 @@ test("bootConfig trackers reach the S3 adapter in the real broker child, which g
 
~
   const f = fixture(root, "acme", (doc) => {
~
     doc.projects = { books: mappingEntry(), ...doc.projects };
~
     
+doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
     doc.vars["tracker.baseUrl"] = srv.url;
~
     doc.tracker.sync.botId = bots.sync;
~
     // The chief of staff (row 119) has no tracker bot.
~
@@ -51,7 +52,7 @@ test("bootConfig trackers reach the S3 adapter in the real broker child, which g
     chmodSync(file, 0o600);
~
   };
~
   put(f.doc.tracker.sync.credentials.vikunja.file, fake.token(bots.sync, SCOPES.sync));
~
   for (const r of Object.keys(f.doc.roles)) if 
-(f.doc.roles[r].credentials)
+(f.doc.roles[r].credentials?.vikunja)
  put(f.doc.roles[r].credentials.vikunja.file, fake.token(bots[r], r === "pm" ? SCOPES.pm : SCOPES.worker));
~
   writeJson(join(root, "project", ".mosaic", "project.json"), { projectVersion: 1, id: "stack", vars: { "tracker.project": project } });
~
 
~
   mkdirSync(f.dataRoot, { recursive: true, mode: 0o700 });
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/control-board/tests/creative-hidden.test.mjs
diff --git a/packages/control-board/tests/creative-hidden.test.mjs b/packages/control-board/tests/creative-hidden.test.mjs
index c2abd348e..12e16866e 100644
--- a/packages/control-board/tests/creative-hidden.test.mjs
+++ b/packages/control-board/tests/creative-hidden.test.mjs
@@ -20,7 +20,9 @@ function fixture(t) {
   const dir = join(root, "config");
~
   const doc = businessDoc(root, "acme");
~
   doc.projects = { books: mappingEntry(), ...doc.projects };
~
   
+doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
   doc.roles.bean = { definition: "companion" };
~
   
+doc.roles.bean.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
   writeJson(join(dir, "businesses", "acme.json"), doc);
~
   const threadDir = join(root, "data", "threads", "acme", "t1", "session");
~
   mkdirSync(threadDir, { recursive: true });
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/discord/tests/library-seat.test.mjs
diff --git a/packages/discord/tests/library-seat.test.mjs b/packages/discord/tests/library-seat.test.mjs
index 315f69698..a7ac869e5 100644
--- a/packages/discord/tests/library-seat.test.mjs
+++ b/packages/discord/tests/library-seat.test.mjs
@@ -31,7 +31,9 @@ function setup(overrides = {}) {
 function business(s, { library, role = "sage", definition = "companion" }) {
~
   const doc = businessDoc(s.root, "acme");
~
   if (library) doc.projects = { books: mappingEntry(), ...doc.projects };
~
   
+if (library) doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
   doc.roles[role] = { definition };
~
   
+doc.roles[role].credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
~
   writeJson(join(s.root, "businesses", "acme.json"), doc);
~
 }
~
 
~
Command: git diff --cached --word-diff=porcelain e0c6142f3 -- packages/harness/tests/creative.test.mjs
diff --git a/packages/harness/tests/creative.test.mjs b/packages/harness/tests/creative.test.mjs
index 80f4516ee..c3191f99e 100644
--- a/packages/harness/tests/creative.test.mjs
+++ b/packages/harness/tests/creative.test.mjs
@@ -24,6 +24,7 @@ function bean(t, definition, { harness = "pi", persona = false, vars = {} } = {}
   return resolvedFor(t, "bean", (d) => {
~
     d.roles.bean = { definition, vars: { harness, model: "claude-sonnet-5-5", thinking: "high",
~
       ...(persona ? { "persona.name": "Bean", "persona.sha256": sha(TEXT) } : {}), ...vars } };
~
     
+d.roles.bean.credentials = { gitea: { ...d.roles.coder.credentials.gitea } };
~
   });
~
 }
~
 
~
