Row 138 round 2, Rocko's candidate e3f9a400 (43 paths) on26a0b0fab. Dewey approved (comment 28754) after round 1 changes (comment 28752): D1-D5 mutants killed, the 17 touched test files run singly passed 124/124. Rocko's own run: 753 tests and 59/59 mutants killed. The bus gains the project adapter through runtime.mjs, the way it already injects the organization, so broker.mjs takes no new imports and refuses with project-not-configured when no adapter is set. The project socket timeout is 120s. Roles gain record.write. The schema moves from 3e to 3f; the live migration follows MIGRATION.md and needs Mos's window. Sage's landing gate ran onc842686d5plus the candidate: 25 steps, all rc 0 (15 packages, 10 script suites), network sage-r138_default removed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
125 lines
7.3 KiB
JavaScript
125 lines
7.3 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, rmSync, writeFileSync, readFileSync, symlinkSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { execFileSync } from 'node:child_process';
|
|
import * as api from '../src/index.mjs';
|
|
const git = (repo, ...args) => execFileSync('git', ['-c', 'core.hooksPath=/dev/null', '-C', repo, ...args], { encoding: 'utf8' }).trim();
|
|
async function fixture(t) {
|
|
const root = mkdtempSync(join(tmpdir(), 'project-spec-read-'));
|
|
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const context = { business: { id: 'demo', human: 'jason', projects: {
|
|
stack: { root: join(root, 'code'), repo: join(root, 'records') },
|
|
} }, project: 'stack', dataRoot: root };
|
|
const { repo, commit } = await api.initProject(context);
|
|
const save = (path, bytes, author = 'jason') => {
|
|
writeFileSync(join(repo, path), bytes);
|
|
git(repo, 'add', '--', path);
|
|
git(repo, '-c', `user.name=${author}`, '-c', '[email protected]', 'commit', '-m', 'Save ' + path);
|
|
return git(repo, 'rev-parse', 'HEAD');
|
|
};
|
|
return { context, repo, commit, save, root };
|
|
}
|
|
|
|
test('spec reads exact committed content and its path commit/log, not dirty bytes or a later docs commit', async t => {
|
|
const { context, repo, save, commit } = await fixture(t);
|
|
const spec = save('spec/north-star.md', '# Spec\n\nKeep this.\n\n');
|
|
const docs = save('docs/notes.md', '# Docs\n');
|
|
writeFileSync(join(repo, 'spec/north-star.md'), '# Unsaved human edit\n');
|
|
const result = await api.readSpec(context);
|
|
assert.equal(result.content, '# Spec\n\nKeep this.\n\n');
|
|
assert.equal(result.repo, repo);
|
|
assert.equal(result.path, 'spec/north-star.md');
|
|
assert.equal(result.head, docs); assert.equal(result.revision, docs); assert.equal(result.commit, spec);
|
|
assert.equal(result.confirmed, true);
|
|
assert.deepEqual(result.author, { name: 'jason', email: '[email protected]' });
|
|
assert.deepEqual(result.log.map(r => r.commit), [spec, commit]);
|
|
assert.equal(result.truncated, false);
|
|
assert.equal(readFileSync(join(repo, 'spec/north-star.md'), 'utf8'), '# Unsaved human edit\n');
|
|
assert.equal(git(repo, 'rev-parse', 'HEAD'), docs);
|
|
for (const row of result.log) assert.equal(new Date(row.at).toISOString(), row.at);
|
|
const historical = await api.readSpec({ ...context, commit });
|
|
assert.equal(historical.commit, commit);
|
|
assert.deepEqual(historical.log.map(r => r.commit), [commit]);
|
|
assert.equal(historical.content, readFileSync(new URL('../templates/north-star.md', import.meta.url), 'utf8'));
|
|
});
|
|
|
|
test('latest agent spec edit is visible but never confirmed or replaced by an earlier human version', async t => {
|
|
const { context, save } = await fixture(t);
|
|
const commit = save('spec/north-star.md', '# Agent\n', 'coder.builder');
|
|
const result = await api.readSpec(context);
|
|
assert.equal(result.commit, commit); assert.equal(result.confirmed, false);
|
|
assert.equal(result.author.name, 'coder.builder');
|
|
});
|
|
|
|
test('shared file reader exposes committed docs and refuses path/revision injection or another history', async t => {
|
|
const { context, repo, save } = await fixture(t);
|
|
const commit = save('docs/design.md', '# Design\n');
|
|
assert.equal((await api.readProjectFile({ ...context, path: 'docs/design.md' })).commit, commit);
|
|
for (const path of ['../outside.md', 'spec/other.md', '/spec/north-star.md', 'docs/a/b.md', 'requirements/R.json'])
|
|
await assert.rejects(api.readProjectFile({ ...context, path }), { code: 'project-read-path' });
|
|
for (const commit of ['HEAD', '--help', 'a'.repeat(40), 'abc', git(repo, 'write-tree')])
|
|
await assert.rejects(api.readSpec({ ...context, commit }), { code: 'project-read-commit' });
|
|
const orphan = git(repo, '-c', 'user.name=jason', '-c', '[email protected]',
|
|
'commit-tree', git(repo, 'write-tree'), '-m', 'Unreachable');
|
|
await assert.rejects(api.readSpec({ ...context, commit: orphan }), { code: 'project-read-commit' });
|
|
});
|
|
|
|
test('reader rejects missing paths, links, invalid UTF-8, NUL and oversized committed blobs', async t => {
|
|
const { context, repo, save } = await fixture(t);
|
|
await assert.rejects(api.readProjectFile({ ...context, path: 'docs/missing.md' }), { code: 'project-read-missing' });
|
|
for (const bytes of [Buffer.from([0xff]), 'x\0y', 'x'.repeat(1024 * 1024 + 1)]) {
|
|
save('spec/north-star.md', bytes);
|
|
await assert.rejects(api.readSpec(context), { code: 'project-read-content' });
|
|
}
|
|
rmSync(join(repo, 'spec/north-star.md'));
|
|
symlinkSync('../docs/README.md', join(repo, 'spec/north-star.md'));
|
|
git(repo, 'add', '--', 'spec/north-star.md');
|
|
git(repo, '-c', 'user.name=jason', '-c', '[email protected]', 'commit', '-m', 'Linked spec');
|
|
await assert.rejects(api.readSpec(context), { code: 'project-read-content' });
|
|
});
|
|
|
|
test('reader refuses mismatched committed project identity, unsafe config and unknown argument keys', async t => {
|
|
const { context, repo, save } = await fixture(t);
|
|
await assert.rejects(api.readSpec({ ...context, path: 'docs/README.md' }), { code: 'project-read-path' });
|
|
await assert.rejects(api.readSpec({ ...context, project: 'other' }), { code: 'project-not-declared' });
|
|
const metadata = JSON.parse(readFileSync(join(repo, 'project.json'))); metadata.business = 'other';
|
|
save('project.json', JSON.stringify(metadata));
|
|
await assert.rejects(api.readSpec(context), { code: 'project-invalid' });
|
|
git(repo, 'remote', 'add', 'origin', 'https://fixture.invalid/no-network');
|
|
await assert.rejects(api.readSpec(context), { code: 'project-remote' });
|
|
});
|
|
|
|
test('path history is bounded at 100 and explicitly marks truncation', async t => {
|
|
const { context, save } = await fixture(t);
|
|
for (let i = 0; i < 101; i++) save('spec/north-star.md', `# Spec ${i}\n`);
|
|
const result = await api.readSpec(context);
|
|
assert.equal(result.log.length, 100);
|
|
assert.equal(result.truncated, true);
|
|
assert.equal(result.log[0].commit, result.commit);
|
|
});
|
|
|
|
test('committed UTF-8 BOM, Unicode and trailing newlines are returned without normalization', async t => {
|
|
const { context, save } = await fixture(t);
|
|
const value = '\uFEFF# North star 🧭\r\n\r\n';
|
|
save('spec/north-star.md', Buffer.from(value));
|
|
assert.equal((await api.readSpec(context)).content, value);
|
|
});
|
|
|
|
|
|
test('D4: spec log refuses a commit subject larger than its 256 KiB read bound', async t => {
|
|
const root = mkdtempSync(join(tmpdir(), 'spec-log-bound-'));
|
|
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const business = { id: 'demo', human: 'jason', projects: { stack: { root: join(root, 'code'), repo: join(root, 'records') } } };
|
|
const { repo } = await api.initProject({ business, project: 'stack', dataRoot: root });
|
|
const message = join(root, 'commit-message.txt');
|
|
writeFileSync(message, 'X'.repeat(256 * 1024 + 1) + '\n');
|
|
writeFileSync(join(repo, 'spec/north-star.md'), '# Small spec\n');
|
|
execFileSync('git', ['-C', repo, 'add', '--', 'spec/north-star.md']);
|
|
execFileSync('git', ['-c', 'core.hooksPath=/dev/null', '-c', 'commit.gpgsign=false',
|
|
'-c', 'user.name=jason', '-c', '[email protected]', '-C', repo, 'commit', '-F', message],
|
|
{ stdio: ['ignore', 'ignore', 'pipe'] });
|
|
await assert.rejects(api.readSpec({ business, project: 'stack', dataRoot: root }), { code: 'project-git' });
|
|
});
|