Compare commits

..
2 Commits
Author SHA1 Message Date
jason.woltje 1616d1dd6f docs: BUILD-LOG and session line for PRD 1.20 and defect l (#1873, #1886) 2026-11-09 17:38:26 -06:00
jason.woltje 7a97cfc4f3 fix(bus): make the private store root at start when a reference names the store (#1886)
Adding an agent with a store credential on an instance with no
<configDir>/secrets/agents left the bus unable to start
(credential-file): Credentials refuses a missing store root, and only
issue() made it, which needs a running bus. Start now makes the root
(0700) the same way issue() does, only when some reference has
store: true. Credentials' own refusal (credential-store T1) is unchanged,
and a root with loose modes or a symlink still refuses start.

Tests: two new credential-verbs tests; the creation test fails on
483eccb3d with credential-file. All 171 bus test files run one at a
time: 1713 pass, 0 fail.
2026-11-09 17:10:09 -06:00
4 changed files with 38 additions and 4 deletions

No files matched your search

+2
View File
@@ -6343,3 +6343,5 @@ R5 quote fallback room (#797 D3, 2026-11-06, MVP3 lead): r4 review P2 found that
2026-11-09 UTC | Organizations friction round, landings #1876-#1881 (#1873) | Before: refactor 00ef0bd30. A Console launched by a relative path showed Request status unavailable, the overview printed the north star as raw markdown, packages/business and the business command spellings were the only names, Quick Setup ended with no route to a north star, an archived org could not be restored from a fresh page, and a dead bus after setup had no browser control. After, each through one Codex Sol xhigh review round plus a lead or builder fix round: #1876 re-execs a relative Console launch with its absolute script path (build 744753d7e, reproduced 121/0; review APPROVE 0/0/1). #1877 F1 renders the north star as sanitized markdown with a short preview (review APPROVE 0/0/1; live retest 4/0 twice). #1878 rename stage B moves packages/business to packages/organization and adds the org command spellings, keeping the business ones and their messages (builder 6828/6, see report; review CHANGES 0/1/0, fixed in e19db4009). #1879 F2 adds the onboarding interview after Quick Setup and on the project page, with human-only north-star approval (`mosaic project north-star --approve`) and no new agent grant (builder 411/0; review CHANGES 0/1/1: a refused start left its button disabled, fixed in 5937a977f along with the P3 stale approval capability). #1880 lands the U0 unified workspace samples, approved by Jason with the four-tile logo kept; the module names stay undecided at his request. #1881 E2E round 5 fixes restore from a fresh page, show an owner bus Start whenever the org list reads the bus down, say room changes are unavailable on org Consoles and recover the composer (builder 258/0; review CHANGES 0/1/0, 231/0: Settings kept Done after the bus died because only opening the org reread health). Lead fix fdd6658a6: every org list read is announced and the open setup redraws on a flip, and app.js reads the list before bus-status so a failed bus-status read cannot skip it; regression red on 8a8edc44b, 18 touched webui files 199/0 (Node 26.12.1, one file at a time). uiorg redeployed at fdd6658a6, 19:53Z. Open follow-ups: a down bus offers both the F3 banner Restart and the Settings Start, focus can move when setup redraws, and a Console stop also stops the org buses it started. Reports: /mnt/storage/scratch/mosaic-lead/reports/org-{reqget-2,f1,rename-b,f2,u0-design,e2e5}-*.md.
2026-11-09 UTC, organizations F3 (#1873, PR #1884, a8f9f0957). The WebUI restarts the Console (supervised Consoles exit and `mosaic console start` relaunches them), restarts an organization's bus from the bus-down banner with a done receipt, and sends the owner's routing, agent, instance, name, assistant, limits and model proxy writes to the selected organization's bus. Routing apply lists how a change reaches each saved agent and offers Restart NAME now. Console restarts, bus restarts and routing, agent and instance changes append to `<dataRoot>/console/actions.jsonl`. Builder plus two refactor merges: combined list 354/0 on 999d173fd. Sol review CHANGES 1/0/1. P1: on a `--dev-local-owner` Console a local agent can send the right Origin and Fetch Metadata headers and reach these controls. The pinned startup warning and the accepted no-auth dev flags already cover this, but the README claimed no agent reaches them. Lead decision: correct the README and file the cooperative local-agent peer refusal as #1883, without building it in this round. P3: the instances route wrote no trail line. Fixed in 0a73279d6; the regression test fails on 999d173fd and passes on the fix. After the refactor merge, 25 touched or README-reading files 195/0 (Node 26.12.1, one file at a time, env -i). uiorg redeployed at a8f9f0957, 22:45Z; live config offers consoleRestart and org settingsSend. Correction to this session's process: after the test run completed I replied "No response requested" and stopped until Jason asked. Open follow-ups: defect l (a bus restart after adding a credentialed agent can refuse to start), the bus restart refusal code, a stale-lock Recover control, one control for banner Restart and Settings Start, #1883. Report: /mnt/storage/scratch/mosaic-lead/reports/org-f3-review.md.
2026-11-09 UTC, PRD 1.20 and defect l (#1873, #1886; PRs #1887 03fe321b1 and #1888 7a97cfc4f). PRD 1.20: Jason approved draft 2 at 22:57Z ("The file looks fine other than that"; the Montage module name stays deferred). The PRD now has 118 requirements, with REQ-WS-1..8, REQ-AGENT-1..3, REQ-SYS-1..2, REQ-ACCT-1..2 and REQ-SETUP-1..2 added and REQ-ASSIST-1, REQ-USER-1, REQ-TASK-1 and REQ-WEB-3 amended. Builder Codex Terra xhigh declined to commit because stack-copy failed 20/1 (node-gyp missing from the test Node's npm); that failure is pre-existing on 483eccb3d, so the lead committed. Sol review APPROVE 0 (word-for-word diff against draft 2, catalog order, both requirements tests). Defect l: before, an agent that declared a store credential made the next bus start refuse, because Credentials refuses a missing store root and only issue() created it. After: start creates the root 0700 when any reference names the store; a root that exists with loose modes or as a symlink still refuses. Two new credential-verbs tests fail on the parent. Bus suite 171 files one at a time, 1713/0 (Node 26.12.1, env -i). Sol review APPROVE 0. uiorg redeployed at 7a97cfc4f, 23:15Z. Follow-up: the stack-copy node-gyp test environment. Reports: /mnt/storage/scratch/mosaic-lead/reports/{prd120-land,prd120-review,defectl-review}.md.
+1
View File
@@ -2122,3 +2122,4 @@ are never rewritten or removed; corrections are new entries.
2026-11-07 UTC | Claude implementation sub-agent | Organizations S6 WebUI create and guided setup on org/s6 | Settings > Organizations: template form with JSON preview, C2 create/update with if-revision, setup cards for checkout, repositories and bus; webui suite 974/975 (inherited dev-owner test failure). Report and screenshots: /mnt/storage/scratch/mosaic-lead/uidev/org-release/report-s6.md. No push, merge or deployment.
2026-11-07 UTC | Codex implementation sub-agent | Organizations release fix round 1 on ui-org-r1 | Local candidate for independent review; verification and findings recorded in /mnt/storage/scratch/mosaic-lead/uidev/org-release/report-fix-r1.md. Queue refused linked worktree; no push, merge or deployment.
2026-11-09 UTC | Mos (lead, Claude Opus 5.5) | Organizations F3 fix round and landing (#1873) | PR #1884 merged a8f9f0957; review P3 fixed, P1 README corrected with follow-up #1883; 195/0 focused suite; uiorg redeployed 22:45Z.
2026-11-09 UTC | Mos (lead, Claude Opus 5.5) | PRD 1.20 landing and defect l (#1873, #1886) | PR #1887 merged 03fe321b1, PR #1888 merged 7a97cfc4f; both Sol reviews APPROVE 0; bus suite 1713/0; uiorg redeployed 23:15Z; U1, U2 and U4a builds dispatched.
+6 -1
View File
@@ -1,7 +1,7 @@
import { createRequirementsHost } from '../../tasks/src/builtin.mjs';
import { createRequirementCatalog } from './requirement-catalog.mjs';
import { readPrdMaterial } from '../../project/src/prd.mjs';
import { lstatSync, readdirSync, readFileSync } from 'node:fs';
import { lstatSync, mkdirSync, readdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { Store } from './store.mjs';
@@ -360,6 +360,11 @@ export async function startBroker({
// Row 236 (#1742, #436b r5 section 5): each gitea grant record gives its agent a store reference to
// gitea, and `held` names the files memory must not load until reconcile or a revoke.
const { held } = storeRoot ? giteaStart(store, storeRoot, businesses, references) : { held: [] };
// #1886 (F3 defect l): a store reference with no file yet reads as no key (design D3a), but Credentials
// refuses a missing store root, and only issue() made it. Start makes it the same way issue() does when
// any reference names the store. A root that exists with loose modes or as a symlink still refuses.
if (storeRoot && Object.values(references).some(refs => Object.values(refs ?? {}).some(ref => ref?.store === true)))
mkdirSync(storeRoot, { recursive: true, mode: 0o700 });
credentials = new Credentials({ references, repoRoots: protectedRoots, dataRoot, storeRoot, held,
modelAgents: proxy ? Object.entries(businesses).flatMap(([id, b]) => Object.keys(b.roles).map(role => id + '/' + role)) : [] });
if (proxy) {
+29 -3
View File
@@ -4,7 +4,7 @@
// scratch root only.
import test from 'node:test';
import assert from 'node:assert/strict';
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs';
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { startBroker } from '../src/runtime.mjs';
@@ -18,7 +18,7 @@ const OTHER = 'fake_other_token_5555555555';
const DATE = '2099-01-01';
const role = { authority: { withinRole: ['message.send'], crossRole: [] } };
async function setup(t) {
async function setup(t, { storeRoot = true } = {}) {
const root = mkdtempSync(join(tmpdir(), 'bus-cred-verbs-'));
t.after(() => rmSync(root, { recursive: true, force: true }));
const rolesDir = rolesCopy(root);
@@ -26,7 +26,7 @@ async function setup(t) {
doc.roles.coder.credentials.gitea = { store: true, rotateBy: DATE };
writeJson(join(root, 'businesses/demo.json'), doc);
mkdirSync(join(root, 'data'), { mode: 0o700 });
mkdirSync(join(root, 'secrets', 'agents'), { mode: 0o700 });
if (storeRoot) mkdirSync(join(root, 'secrets', 'agents'), { mode: 0o700 });
const store = (agent, service) => join(root, 'secrets', 'agents', 'demo', agent, service);
// The bus's resolved business carries the same references; a store one names its derived file.
const demo = {
@@ -161,3 +161,29 @@ test('T5: issue, rotate, revoke and delete are live, rewrite the business file a
for (const path of walk(c.root))
for (const token of [TOKEN, ROTATED, OTHER]) assert.equal(readFileSync(path).includes(token), false, path);
});
test('#1886: a store reference with no store root yet starts the bus, makes the root private and issues', async (t) => {
const c = await setup(t, { storeRoot: false });
const dir = join(c.root, 'secrets', 'agents');
assert.equal(statSync(dir).mode & 0o777, 0o700);
assert.equal(statSync(join(c.root, 'secrets')).mode & 0o077, 0);
assert.deepEqual(c.status('coder'), { gitea: 'not-issued', vikunja: 'valid' });
const issued = c.ask(c.human, 'credentials.issue', { agent: 'coder', service: 'gitea', token: TOKEN + '\n', date: DATE });
assert.deepEqual(issued, { agent: 'coder', verb: 'issue', service: 'gitea', date: DATE, state: 'valid' });
assert.equal(statSync(c.store('coder', 'gitea')).mode & 0o777, 0o600);
});
test('#1886: a store root that exists with loose modes still refuses start', async (t) => {
const root = mkdtempSync(join(tmpdir(), 'bus-cred-loose-'));
t.after(() => rmSync(root, { recursive: true, force: true }));
const rolesDir = rolesCopy(root);
writeJson(join(root, 'businesses/demo.json'), { ...businessDoc(root, 'demo'), plugins: { harness: ['pi'] } });
mkdirSync(join(root, 'data'), { mode: 0o700 });
mkdirSync(join(root, 'secrets', 'agents'), { recursive: true, mode: 0o750 });
chmodSync(join(root, 'secrets', 'agents'), 0o750);
const demo = {
id: 'demo', human: 'jason', arbiters: { technical: 'coder', delivery: 'coder' }, vars: {},
roles: { coder: { ...role, credentials: { gitea: { store: true, file: join(root, 'secrets', 'agents', 'demo', 'coder', 'gitea'), rotateBy: DATE } } } },
};
await assert.rejects(startBroker({ dataRoot: join(root, 'data'), businesses: { demo }, organization: { dir: root, rolesDir, system: systemFor(root) } }), /credential-file/);
});