row 217: early-review coverage and availability RED cases (Untested)
This commit is contained in:
1 parent
c25f5b8dae
commit
b6c3129d2e
11 files changed
+212
-6
No files matched your search
@@ -5415,3 +5415,5 @@ Row 299 (573, #1806, Gander, Filbert): before, the P10 band test timed a fault r
|
|||||||
2026-10-23 | Beakley (88b97f65) | Row 217 #1718 takeover, before implementation | Clean base 45dca69f6 on beakley/row217. Recovering Launchpad handoff for citation guard, live settings, page store and runner retry. Acceptance is C2-06/design 5.2 plus sage-3595/3599/3602. Tracking agents/beakley/work/queue-217/BUILD.md; no product changes or Node/tests; Sage CLEAR required.
|
2026-10-23 | Beakley (88b97f65) | Row 217 #1718 takeover, before implementation | Clean base 45dca69f6 on beakley/row217. Recovering Launchpad handoff for citation guard, live settings, page store and runner retry. Acceptance is C2-06/design 5.2 plus sage-3595/3599/3602. Tracking agents/beakley/work/queue-217/BUILD.md; no product changes or Node/tests; Sage CLEAR required.
|
||||||
|
|
||||||
2026-10-23 | Beakley (88b97f65) | Row 217 #1718 untested draft checkpoint | Continued Launchpad e25ef0373; added runner retry/text/note implementation, pure protocol constants and sage-3640 approved single read-only mount, CLI documentation and new acceptance test files. Static checks caught missing projects compatibility, async request mutation and Git pathspec matching; bounded fixes and regression tests authored. git diff --check clean. No Node/test/mutant executed; Sage holds CLEAR. 283/216/285 dependency checks and all acceptance execution remain open. Packet agents/beakley/work/queue-217/MANIFEST.md.
|
2026-10-23 | Beakley (88b97f65) | Row 217 #1718 untested draft checkpoint | Continued Launchpad e25ef0373; added runner retry/text/note implementation, pure protocol constants and sage-3640 approved single read-only mount, CLI documentation and new acceptance test files. Static checks caught missing projects compatibility, async request mutation and Git pathspec matching; bounded fixes and regression tests authored. git diff --check clean. No Node/test/mutant executed; Sage holds CLEAR. 283/216/285 dependency checks and all acceptance execution remain open. Packet agents/beakley/work/queue-217/MANIFEST.md.
|
||||||
|
|
||||||
|
2026-10-23 | Beakley (88b97f65) | Row 217 early-look follow-up, Untested | Webby 0939c0682 E1 adds ordinary mallory human-swap test; E2 adds stored trailer-derived agent/file event evidence; E6 creative-sender matrix and E7 runtime readRaw tests authored. Sage-3668/3670 accepts direct writer substitution because setter keys exclude replyLimits (organization.set exists, correcting review premise), and requires distinct availability refusal for E4. Real held-lock/socket and runner-reason regressions plus M5 authored before that production fix; expected RED awaits Sage CLEAR. No Node/tests/mutants, no old-test edits, no green claim.
|
||||||
@@ -105,3 +105,48 @@ fetch of the cited URL. Each has new-file regression coverage.
|
|||||||
- No queue changes, protected branch pushes, gate claims or acceptance claims.
|
- No queue changes, protected branch pushes, gate claims or acceptance claims.
|
||||||
|
|
||||||
Static obsolete-code/key search completed: only packages/library/src/render.mjs:6 and packages/library/tests/render.test.mjs:58 match no-cited-answer; both belong to row 225 per standing ruling. No owned-path hit. Repeat before in-review after dependency changes.
|
Static obsolete-code/key search completed: only packages/library/src/render.mjs:6 and packages/library/tests/render.test.mjs:58 match no-cited-answer; both belong to row 225 per standing ruling. No owned-path hit. Repeat before in-review after dependency changes.
|
||||||
|
|
||||||
|
|
||||||
|
## Webby early look 0939c0682 / webby-0249 and sage-3668/3670
|
||||||
|
|
||||||
|
This remains an untested draft. The follow-up adds tests only; production E4
|
||||||
|
changes wait for the held-lock regression's first RED under Sage CLEAR.
|
||||||
|
|
||||||
|
- E1: added an ordinary actor `mallory` that sets both `human` to itself and
|
||||||
|
`replyLimits`. The existing role/tester actors did not distinguish M2 because
|
||||||
|
the actor-shape exclusions rejected them independently of the human comparison.
|
||||||
|
M2 is still unrun; no kill claimed.
|
||||||
|
- E2: added a real bus event assertion for `agent: "acme/scribe"` from a file's
|
||||||
|
Mosaic-Agent trailer. The cited tip includes a later unrelated commit, so the
|
||||||
|
event must store the file commit rather than the library tip. thread.messages
|
||||||
|
must show `last === file` for that unchanged path. Supporting scope still waits
|
||||||
|
for row 283's real fixture, as ruled.
|
||||||
|
- E3: sage-3668 accepts direct writer.write tests under chief and tester actors
|
||||||
|
as substitutes for the brief's setter cases (brief lines 483-491 at base
|
||||||
|
45dca69f6). Correction to the early-look premise: organization.set DOES exist,
|
||||||
|
broker.mjs:4701/4709, but ORGANIZATION_KEYS excludes replyLimits; instances.set
|
||||||
|
also cannot carry it. Direct writes exercise the shared M11 boundary those
|
||||||
|
setters cannot reach with that input. Sage confirmed the accurate reason in
|
||||||
|
sage-3670; the unrelated actor case remains required.
|
||||||
|
- E4: traced readSyncState -> withLibraryLock -> library-sync-busy after five
|
||||||
|
seconds -> servedMirror catch-all -> null served -> misleading citation-invalid.
|
||||||
|
New real-flock/socket test first proves the cite valid, holds the lock, asserts
|
||||||
|
the exact citation-unavailable reason the agent receives, verifies no insert,
|
||||||
|
and checks fixed fallback delivery and recovery after unlock. Additional tests
|
||||||
|
distinguish malformed sync state from absent/new mirrors, and assert that the
|
||||||
|
actual runner retry request contains that reason. These tests are expected RED
|
||||||
|
until the fix. Planned production change: only an absent mirror or legitimate
|
||||||
|
empty state yields null; other failures become GuardError citation-unavailable;
|
||||||
|
bodies without path cites do not require the mirror lock. Add the code to
|
||||||
|
GUARD_CODES so the runner sends the agent that reason on its one retry. Per
|
||||||
|
sage-3670, existing four fallback bodies can remain only if that delivery is
|
||||||
|
verified. M5 restores catch-all behavior; authored, not run.
|
||||||
|
- E5: first-library-project selection is a Launchpad handoff implementation
|
||||||
|
choice, not a validated rule that a business can only have one library.
|
||||||
|
Cites into another library remain a limitation. No mapping/schema rule added.
|
||||||
|
- E6: the broker's full guard table now runs as both coder and research.
|
||||||
|
- E7: new citation-runtime.test.mjs starts the real runtime, reads limits from
|
||||||
|
the real organization file, then changes, corrupts and removes that fixture
|
||||||
|
file and checks library.status without replacing the library adapter.
|
||||||
|
|
||||||
|
No Node, tests or mutants ran. No test that predated row 217 changed.
|
||||||
@@ -55,3 +55,14 @@ drift must be checked against 216/285 before in-review. Landing follows 283.
|
|||||||
Full gate: library, business, bus, cli, harness and seat, plus every
|
Full gate: library, business, bus, cli, harness and seat, plus every
|
||||||
scripts/test-*.sh. Webby reviews the final tested manifest; Sage owns the gate,
|
scripts/test-*.sh. Webby reviews the final tested manifest; Sage owns the gate,
|
||||||
queue and landing. No push to a protected branch is authorized.
|
queue and landing. No push to a protected branch is authorized.
|
||||||
|
|
||||||
|
|
||||||
|
## Early-look follow-up packet (manifest.r2.sha256)
|
||||||
|
|
||||||
|
Tests-only follow-up to Webby 0939c0682. Adds ordinary-actor M2 discrimination,
|
||||||
|
stored agent/file evidence at an unrelated tip, creative-sender table coverage,
|
||||||
|
and real runtime readRaw coverage. New citation-availability.test.mjs and runner
|
||||||
|
availability case are intentionally unrun regressions for E4/sage-3668/3670.
|
||||||
|
Production availability handling is not implemented yet: run that targeted RED
|
||||||
|
on CLEAR before applying its fix, then rerun the same cases. M5 restores the
|
||||||
|
catch-all. This packet is not ready for a green gate or manifest approval.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
68c05644d29188b897e2d4695d8e7025e143d424d69a91bc07bc950f589d34bf packages/bus/src/broker.mjs
|
||||||
|
ad3c686a089f67f3c89610937c6fc9b32367cea5d9735125d89bef042082d4e8 packages/bus/src/client.mjs
|
||||||
|
49e01e5e73cc3e830ad48a1b6b13fe4d06a38950f17847dc049b75209d6d3f0a packages/bus/src/runtime.mjs
|
||||||
|
66b26d131a19f4f411686427274936850c1ff0aed11e9b281ae4aadcf4b289fc packages/bus/src/server.mjs
|
||||||
|
cdd0304ec25ef1ad3e31c52502fa46f2c80bd77c8d3c2e13f8ed4b5328f342c9 packages/bus/tests/citation-availability.test.mjs
|
||||||
|
8dc344b8a905c58d544b031ca84adb7eb869d9de88eb8e6b0fc1d9190702a38a packages/bus/tests/citation-guard.test.mjs
|
||||||
|
62d8a55945d4b700e22c8c49b6420a5e25719d266901a2ad20c72514735e65e9 packages/bus/tests/citation-helpers.mjs
|
||||||
|
168dcac99d9bfdfdf5ff0eaa2c0fff91595b351b4d0092dc299532c9c32869f0 packages/bus/tests/citation-runtime.test.mjs
|
||||||
|
825da818ba3cf45a4837f0f15a9c2cdba55a3d0bbb4479d55236f35799427c6f packages/business/src/business.mjs
|
||||||
|
dbd79a4dfc926620d034b540f98f39336d81e4c79f214424c3cf19250c1d1f8d packages/business/src/organization.mjs
|
||||||
|
4d2eb11beaa80175e524af1b95c81e7164990fba1d3e1754f5ba06eee390a14f packages/business/tests/reply-limits.test.mjs
|
||||||
|
1f1bea250f694a2aae9b9dc856b3c3424ceca153ccf826c99ccb98e6ea9cd394 packages/cli/README.md
|
||||||
|
2f291a71f490f2741edada7a3e3788500f8976a08eb8ab1411f6c5982f778260 packages/cli/src/limits.mjs
|
||||||
|
f38af2d46d5f5f6388f599912507b94b87a0bf9dcc2104bb2187c38ee35118d5 packages/cli/tests/reply-limits.test.mjs
|
||||||
|
6ad72b4245e0a66e32725941900c1ece64c2e5de2fb73d65c8e042b4f3a691a6 packages/harness/src/runner.mjs
|
||||||
|
a5ef1b65230239a7baf6d0e8f2b8a51c0516348c7c4339a55b3f0a1a6ff531ba packages/harness/tests/citation-runner.test.mjs
|
||||||
|
330bc71bf6c7e64d3ddb70440903d2d9acf7878d44a49d7e30c2352ee4c807ec packages/harness/tests/fixtures/citation-adapter.mjs
|
||||||
|
0365114cf5773cbaa9fba196fbd1e773b2ec5ad2300c40231c357ca58884b9e3 packages/library/src/guard.mjs
|
||||||
|
aaa8f7ba60a729a88a6ef57b3d5ff5a69bcf0e17b60e7db51a8dc5a697627b6c packages/library/src/index.mjs
|
||||||
|
3e04fa25a64334d33ffbec704205b32cd01992f2d341e908f933a345199beee0 packages/library/src/limits.mjs
|
||||||
|
c9e682eff87e822647921288d7fce0ebdc7b09f0a6dfdd6528f10249ae26e81b packages/library/src/reply-protocol.mjs
|
||||||
|
dd056409712113eb9c1b70e29b21022f5b30df6ffbfb2658e293edd6ec7195c7 packages/library/tests/guard.test.mjs
|
||||||
|
2f3ec99129d16394d4458e252b9cba4e38ff66cd9393e3889f8afc3140e15a8e packages/seat/src/sandbox.mjs
|
||||||
@@ -31,6 +31,9 @@ MUTANTS = [
|
|||||||
["packages/business/tests/reply-limits.test.mjs"]),
|
["packages/business/tests/reply-limits.test.mjs"]),
|
||||||
("M3-split-check-transaction", [(BROKER, ATOMIC, ""), (BROKER, REENTER, SPLIT)],
|
("M3-split-check-transaction", [(BROKER, ATOMIC, ""), (BROKER, REENTER, SPLIT)],
|
||||||
["packages/bus/tests/citation-guard.test.mjs"]),
|
["packages/bus/tests/citation-guard.test.mjs"]),
|
||||||
|
("M5-hide-library-unavailable", [("packages/library/src/guard.mjs",
|
||||||
|
"refuse('citation-unavailable', 'The library is busy or unavailable. Retry the answer when the library is available.');",
|
||||||
|
"served = null;")], ["packages/bus/tests/citation-availability.test.mjs"]),
|
||||||
("M4-drop-client-reason", [(CLIENT, "new BusError(r.error, typeof r.reason === 'string' ? r.reason.slice(0, 300) : undefined)", "new BusError(r.error)")],
|
("M4-drop-client-reason", [(CLIENT, "new BusError(r.error, typeof r.reason === 'string' ? r.reason.slice(0, 300) : undefined)", "new BusError(r.error)")],
|
||||||
["packages/bus/tests/citation-guard.test.mjs"]),
|
["packages/bus/tests/citation-guard.test.mjs"]),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// E4/sage-3668: real flock contention must not turn a valid cite into an invalid-cite refusal.
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { ensureMirror, mirrorPath, SYNC_STATE_FILE, withLibraryLock } from '../../library/src/mirror.mjs';
|
||||||
|
import { guardReply } from '../../library/src/guard.mjs';
|
||||||
|
import { git } from '../../library/tests/helpers.mjs';
|
||||||
|
import { Client } from '../src/client.mjs';
|
||||||
|
import { serve } from '../src/server.mjs';
|
||||||
|
import { citationWorld, citedBody, fixtureCite } from './citation-helpers.mjs';
|
||||||
|
|
||||||
|
test('C2-06 held library lock gives the agent citation-unavailable and its retry reason', { timeout: 25000 }, async t => {
|
||||||
|
const f = citationWorld(t), dataRoot = join(f.root, 'data'); mkdirSync(dataRoot, { mode: 0o700 });
|
||||||
|
const context = { business: f.business, project: 'books', dataRoot };
|
||||||
|
await ensureMirror(context); const mirror = mirrorPath(context);
|
||||||
|
git(f.repo, '-c', 'protocol.file.allow=always', 'push', '-q', mirror, 'HEAD:refs/heads/served');
|
||||||
|
writeFileSync(join(mirror, SYNC_STATE_FILE), JSON.stringify({ version: 1, branch: 'main', served: f.commit,
|
||||||
|
lastFetch: null, lastError: null, diverged: null, problems: [] }), { mode: 0o600 });
|
||||||
|
f.adapter.guard = (ctx, body, opts) => guardReply({ ...ctx, dataRoot }, body, opts);
|
||||||
|
f.call(f.agent, 'role.claim');
|
||||||
|
const socket = join(f.root, 'availability.sock'), server = await serve({ broker: f.broker, path: socket });
|
||||||
|
t.after(() => server.close()); const client = new Client({ path: socket, cap: f.agent, timeout: 15000 });
|
||||||
|
const args = { to: 'human', body: citedBody([fixtureCite(f.commit)]) };
|
||||||
|
await client.call('message.send', args); // Establish that the cite and real mirror are valid.
|
||||||
|
const before = f.store.get('SELECT count(*) n FROM messages').n;
|
||||||
|
await withLibraryLock(mirror, async () => {
|
||||||
|
await assert.rejects(client.call('message.send', args), e => {
|
||||||
|
assert.equal(e.code, 'citation-unavailable');
|
||||||
|
assert.equal(e.reason, 'The library is busy or unavailable. Retry the answer when the library is available.');
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
assert.equal(f.store.get('SELECT count(*) n FROM messages').n, before);
|
||||||
|
// A fixed fallback carries no library cite and must not need the contested lock.
|
||||||
|
await client.call('message.send', { to: 'human', body: '(no answer passed the citation check)' });
|
||||||
|
});
|
||||||
|
await client.call('message.send', args); // No lock mutation or lost valid cite after release.
|
||||||
|
});
|
||||||
|
|
||||||
|
test('C2-06 malformed sync state is availability failure, never a fabricated invalid cite', async t => {
|
||||||
|
const f = citationWorld(t), dataRoot = join(f.root, 'data'); mkdirSync(dataRoot, { mode: 0o700 });
|
||||||
|
const context = { business: f.business, project: 'books', dataRoot };
|
||||||
|
await ensureMirror(context); const mirror = mirrorPath(context);
|
||||||
|
writeFileSync(join(mirror, SYNC_STATE_FILE), '{broken', { mode: 0o600 });
|
||||||
|
await assert.rejects(guardReply(context, citedBody([fixtureCite(f.commit)]), {
|
||||||
|
limits: f.limits(), definition: 'coder', pages: [],
|
||||||
|
}), { code: 'citation-unavailable' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('C2-06 no mirror or freshly initialized empty mirror still means no served citation', async t => {
|
||||||
|
const f = citationWorld(t), dataRoot = join(f.root, 'data'); mkdirSync(dataRoot, { mode: 0o700 });
|
||||||
|
const context = { business: f.business, project: 'books', dataRoot };
|
||||||
|
const check = () => guardReply(context, citedBody([fixtureCite(f.commit)]), { limits: f.limits(), definition: 'coder', pages: [] });
|
||||||
|
await assert.rejects(check(), { code: 'citation-invalid' });
|
||||||
|
await ensureMirror(context); await assert.rejects(check(), { code: 'citation-invalid' });
|
||||||
|
});
|
||||||
@@ -12,8 +12,8 @@ import { businessDoc, rolesCopy, writeJson } from '../../business/tests/helpers.
|
|||||||
import { resolveReplyLimits } from '../../library/src/limits.mjs';
|
import { resolveReplyLimits } from '../../library/src/limits.mjs';
|
||||||
import { citationWorld, citedBody, fixtureCite, guardCases } from './citation-helpers.mjs';
|
import { citationWorld, citedBody, fixtureCite, guardCases } from './citation-helpers.mjs';
|
||||||
|
|
||||||
test('C2-06 send_message guard covers every table refusal and a non-creative sender; inserts nothing', async t => {
|
for (const definition of ['coder', 'research']) test(`C2-06 ${definition} send_message guard covers every table refusal; inserts nothing`, async t => {
|
||||||
const f = citationWorld(t); f.call(f.agent, 'role.claim');
|
const f = citationWorld(t, { definition }); f.call(f.agent, 'role.claim');
|
||||||
writeFileSync(f.settings, JSON.stringify({ replyLimits: { requireCitation: true } }));
|
writeFileSync(f.settings, JSON.stringify({ replyLimits: { requireCitation: true } }));
|
||||||
for (const [code, body] of guardCases(f.commit)) {
|
for (const [code, body] of guardCases(f.commit)) {
|
||||||
const count = f.store.get('SELECT count(*) n FROM messages').n;
|
const count = f.store.get('SELECT count(*) n FROM messages').n;
|
||||||
@@ -27,6 +27,22 @@ test('C2-06 send_message guard covers every table refusal and a non-creative sen
|
|||||||
assert.deepEqual(JSON.parse(row.body).check, { citations: [{ scope: 'canon', agent: null, file: f.commit }] });
|
assert.deepEqual(JSON.parse(row.body).check, { citations: [{ scope: 'canon', agent: null, file: f.commit }] });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('C2-06 stored check carries the file author trailer at an unrelated cited tip', async t => {
|
||||||
|
const f = citationWorld(t, { definition: 'research' }); f.call(f.agent, 'role.claim');
|
||||||
|
const path = 'Book One/Characters/Mara Vell.md';
|
||||||
|
writeFileSync(join(f.repo, path), readFileSync(join(f.repo, path), 'utf8') + 'A fixture note.\n');
|
||||||
|
git(f.repo, 'add', '-A'); git(f.repo, 'commit', '-qm', 'Fixture note\n\nMosaic-Agent: acme/scribe');
|
||||||
|
const file = git(f.repo, 'rev-parse', 'HEAD');
|
||||||
|
writeFileSync(join(f.repo, 'Inbox Note.md'), 'Unrelated fixture.\n'); git(f.repo, 'add', '-A'); git(f.repo, 'commit', '-qm', 'unrelated');
|
||||||
|
const cited = git(f.repo, 'rev-parse', 'HEAD'); f.setServed(cited);
|
||||||
|
assert.notEqual(cited, file);
|
||||||
|
const sent = await f.call(f.agent, 'message.send', { to: 'human', body: citedBody([fixtureCite(cited)]) });
|
||||||
|
const event = f.store.get("SELECT body FROM events WHERE subject=? AND json_extract(body,'$.action')='message.checked'", sent.id);
|
||||||
|
assert.deepEqual(JSON.parse(event.body).check, { citations: [{ scope: 'canon', agent: 'acme/scribe', file }] });
|
||||||
|
const [row] = await f.call(f.human, 'thread.messages', { thread: f.thread.id });
|
||||||
|
assert.deepEqual(row.check.citations, [{ scope: 'canon', agent: 'acme/scribe', file, last: file }]);
|
||||||
|
});
|
||||||
|
|
||||||
test('C2-06 check evidence and insert are atomic when storing evidence fails', async t => {
|
test('C2-06 check evidence and insert are atomic when storing evidence fails', async t => {
|
||||||
const f = citationWorld(t); f.call(f.agent, 'role.claim');
|
const f = citationWorld(t); f.call(f.agent, 'role.claim');
|
||||||
f.store.run(`CREATE TRIGGER fixture_check_refused BEFORE INSERT ON events
|
f.store.run(`CREATE TRIGGER fixture_check_refused BEFORE INSERT ON events
|
||||||
|
|||||||
@@ -25,13 +25,12 @@ export function citationWorld(t, { definition = 'coder', library = true, guard:
|
|||||||
catch { return resolveReplyLimits({ readable: false }); }
|
catch { return resolveReplyLimits({ readable: false }); }
|
||||||
};
|
};
|
||||||
let served = r.commit;
|
let served = r.commit;
|
||||||
const reader = libraryReader(r.repo, r.commit, map);
|
|
||||||
const reads = [];
|
const reads = [];
|
||||||
const adapter = {
|
const adapter = {
|
||||||
GuardError,
|
GuardError,
|
||||||
replyLimits: limits,
|
replyLimits: limits,
|
||||||
guard: overrideGuard ?? ((context, body, opts) => checkReply(body, {
|
guard: overrideGuard ?? ((context, body, opts) => checkReply(body, {
|
||||||
limits: opts.limits, mapping: map, library: reader,
|
limits: opts.limits, mapping: map, library: libraryReader(r.repo, served, map),
|
||||||
web: { allowed: ['companion', 'research'].includes(opts.definition), pages: opts.pages },
|
web: { allowed: ['companion', 'research'].includes(opts.definition), pages: opts.pages },
|
||||||
})),
|
})),
|
||||||
last: async (context, paths) => {
|
last: async (context, paths) => {
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// 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, 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.replyLimits = { requireCitation: false, citationsPerReply: 3 };
|
||||||
|
const file = writeJson(join(dir, 'businesses/acme.json'), doc);
|
||||||
|
const business = validateBusinessDocument(doc, file, { rolesDir }); mkdirSync(dataRoot, { mode: 0o700 });
|
||||||
|
const runtime = await startBroker({ dataRoot, businesses: { acme: business }, organization: { dir, rolesDir } });
|
||||||
|
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');
|
||||||
|
});
|
||||||
@@ -47,6 +47,13 @@ test('C2-06 M11 serialization cannot replace settings after the first fence', t
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// E1: unlike tester/role actors, mallory passes the other actor-shape exclusions.
|
||||||
|
// Comparing document.human instead of old.human must fail this case.
|
||||||
|
test('C2-06 M11 an unrelated actor cannot appoint itself human and change reply limits', t => {
|
||||||
|
const f = fixture(t);
|
||||||
|
refused(f, 'mallory', d => { d.human = 'mallory'; d.replyLimits.requireCitation = true; });
|
||||||
|
});
|
||||||
|
|
||||||
test('C2-06 invalid setter values preserve the business and changes.jsonl', t => {
|
test('C2-06 invalid setter values preserve the business and changes.jsonl', t => {
|
||||||
const f = fixture(t), before = readFileSync(f.file), log = readFileSync(f.log);
|
const f = fixture(t), before = readFileSync(f.file), log = readFileSync(f.log);
|
||||||
for (const replyLimits of [{ citationsPerReply: 0 }, { citationsPerReply: 1001 }, { requireCitation: 'true' }, { invented: true }, []]) {
|
for (const replyLimits of [{ citationsPerReply: 0 }, { citationsPerReply: 1001 }, { requireCitation: 'true' }, { invented: true }, []]) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { serve } from '../../bus/src/server.mjs';
|
import { serve } from '../../bus/src/server.mjs';
|
||||||
import { FIXED_TEXTS } from '../../library/src/reply-protocol.mjs';
|
import { FIXED_TEXTS } from '../../library/src/reply-protocol.mjs';
|
||||||
|
import { GuardError } from '../../library/src/guard.mjs';
|
||||||
import { citationWorld, citedBody, fixtureCite, guardCases } from '../../bus/tests/citation-helpers.mjs';
|
import { citationWorld, citedBody, fixtureCite, guardCases } from '../../bus/tests/citation-helpers.mjs';
|
||||||
|
|
||||||
const RUNNER = new URL('../src/runner.mjs', import.meta.url).pathname;
|
const RUNNER = new URL('../src/runner.mjs', import.meta.url).pathname;
|
||||||
@@ -16,8 +17,8 @@ async function until(fn) {
|
|||||||
throw Error('fixture timed out');
|
throw Error('fixture timed out');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setup(t, stepsFor, { library = true, requireCitation = true, sendAuthority = true } = {}) {
|
async function setup(t, stepsFor, { library = true, requireCitation = true, sendAuthority = true, guard } = {}) {
|
||||||
const f = citationWorld(t, { library, sendAuthority }), runDir = join(f.root, 'run'), bundle = join(runDir, 'bundle');
|
const f = citationWorld(t, { library, sendAuthority, guard }), runDir = join(f.root, 'run'), bundle = join(runDir, 'bundle');
|
||||||
mkdirSync(bundle, { recursive: true }); mkdirSync(join(f.root, 'ws'));
|
mkdirSync(bundle, { recursive: true }); mkdirSync(join(f.root, 'ws'));
|
||||||
const steps = stepsFor(f.commit), brokerSocket = join(f.root, 'broker.sock');
|
const steps = stepsFor(f.commit), brokerSocket = join(f.root, 'broker.sock');
|
||||||
writeFileSync(f.settings, JSON.stringify({ replyLimits: { requireCitation } }));
|
writeFileSync(f.settings, JSON.stringify({ replyLimits: { requireCitation } }));
|
||||||
@@ -127,3 +128,17 @@ test('C2-06 outside library a refused send keeps the existing no-retry behavior'
|
|||||||
assert.equal(f.count(), 1); assert.equal(f.store.get("SELECT count(*) n FROM messages WHERE from_role='coder'").n, 0);
|
assert.equal(f.count(), 1); assert.equal(f.store.get("SELECT count(*) n FROM messages WHERE from_role='coder'").n, 0);
|
||||||
assert.equal(existsSync(join(f.runDir, 'fixture-requests.jsonl')), true);
|
assert.equal(existsSync(join(f.runDir, 'fixture-requests.jsonl')), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// E4: the agent must receive the availability reason in its actual retry turn,
|
||||||
|
// rather than being told only that the source citation was invalid.
|
||||||
|
test('C2-06 the retry turn delivers citation-unavailable reason to the agent', async t => {
|
||||||
|
let sends = 0;
|
||||||
|
const reason = 'The library is busy or unavailable. Retry the answer when the library is available.';
|
||||||
|
const f = await setup(t, commit => [{ text: citedBody([fixtureCite(commit)]) }, { text: citedBody([fixtureCite(commit)]) }], {
|
||||||
|
guard: async () => { if (sends++ === 0) throw new GuardError('citation-unavailable', reason); return null; },
|
||||||
|
});
|
||||||
|
await f.ask(); assert.equal(f.count(), 2);
|
||||||
|
const requests = readFileSync(join(f.runDir, 'fixture-requests.jsonl'), 'utf8').trim().split('\n').map(JSON.parse);
|
||||||
|
assert.equal(requests[1].includes(`citation-unavailable: ${reason}`), true);
|
||||||
|
});
|
||||||
Reference in new issue
Block a user