docs(slice1): filbert row 38 S3 round 2 review record (approve)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Row 38 sequential gate; $1 = tree, $2 = out prefix
|
# Row 38 sequential gate; $1 = tree, $2 = out prefix
|
||||||
T="$1"; P="$2"; O=~/filbert-scratch/r38/out; cd "$T"
|
T="$1"; P="$2"; O=~/filbert-scratch/r38b/out; cd "$T"
|
||||||
for p in tasks bus business discord; do
|
for p in tasks bus business discord; do
|
||||||
[ -d packages/$p/tests ] || continue
|
[ -d packages/$p/tests ] || continue
|
||||||
env -u NODE_TEST_CONTEXT node --test "packages/$p/tests/*.test.mjs" > "$O/$P-node-$p.txt" 2>&1; echo "$P node-$p exit $? load $(cut -d' ' -f1 /proc/loadavg)"
|
env -u NODE_TEST_CONTEXT node --test "packages/$p/tests/*.test.mjs" > "$O/$P-node-$p.txt" 2>&1; echo "$P node-$p exit $? load $(cut -d' ' -f1 /proc/loadavg)"
|
||||||
done
|
done
|
||||||
for s in scripts/test-*.sh; do
|
for s in scripts/test-*.sh; do
|
||||||
n=$(basename "$s" .sh); n=${n#test-}
|
n=$(basename "$s" .sh); n=${n#test-}
|
||||||
env -u NODE_TEST_CONTEXT DOCKER_HOST=unix:///nonexistent-filbert-r38.sock timeout 900 bash "$s" > "$O/$P-suite-$n.txt" 2>&1; echo "$P suite-$n exit $? load $(cut -d' ' -f1 /proc/loadavg)"
|
env -u NODE_TEST_CONTEXT DOCKER_HOST=unix:///nonexistent-filbert-r38b.sock timeout 900 bash "$s" > "$O/$P-suite-$n.txt" 2>&1; echo "$P suite-$n exit $? load $(cut -d' ' -f1 /proc/loadavg)"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -86,3 +86,10 @@ run B6 $B "s/this\.#secretCheck\(result\);\n return result;/return result;
|
|||||||
SV=packages/bus/src/server.mjs
|
SV=packages/bus/src/server.mjs
|
||||||
run B7 $SV "s/socket\.setTimeout\(tasks\.timeout\);//"
|
run B7 $SV "s/socket\.setTimeout\(tasks\.timeout\);//"
|
||||||
run B8 $SV "s/if \(own\) broker\.disconnect\(own\);//"
|
run B8 $SV "s/if \(own\) broker\.disconnect\(own\);//"
|
||||||
|
# Round 2 additions (the r2 fixes)
|
||||||
|
run N1 $V "s/throw landed \? new BusError\('write-uncertain'\) : failure;/throw failure;/"
|
||||||
|
run N2 $V "s/await step\(role, method, path, body, id, done\);\n(\s*)landed\+\+;/await step(role, method, path, body, id, done);/"
|
||||||
|
run N3 $V "s/const fields = \{ \.\.\.taskFields\(r\.json, ctx\.view\.ids\.todo\), labels: sorted\(plan\.labels\) \};/const fields = taskFields(r.json, ctx.view.ids.todo);/"
|
||||||
|
run N4 $V "s/return x === null \? null : \`\\$\{x\.slice\(0, 19\)\}\.000Z\`;/return x;/"
|
||||||
|
run N5 $V "s/if \(!after && failure\) throw/if (failure) throw/"
|
||||||
|
run N6 $V "s/if \(!after && !failure\) \{/if (false) {/"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Filbert, row 38 round 1 probes. Run from the candidate tree:
|
// Filbert, row 38 probes (round 1, with round 2 additions P1 to P3 at the end; D1 now expects the fix). Run from the candidate tree:
|
||||||
// node --test ~/filbert-scratch/r38/probe.test.mjs (with CAND set to the candidate root)
|
// node --test ~/filbert-scratch/r38/probe.test.mjs (with CAND set to the candidate root)
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
@@ -45,7 +45,7 @@ test('D1 schedule with milliseconds: the poll reports the bot\'s own due date as
|
|||||||
// The pm's next call with the digest it was handed now conflicts.
|
// The pm's next call with the digest it was handed now conflicts.
|
||||||
const c = await code(w.call(pm, 'task.priority.change', { task_ref, priority: 2, expect: r.digest, decision: 'x' }));
|
const c = await code(w.call(pm, 'task.priority.change', { task_ref, priority: 2, expect: r.digest, decision: 'x' }));
|
||||||
console.log('D1 expect=returned digest ->', c);
|
console.log('D1 expect=returned digest ->', c);
|
||||||
assert.equal(ext.length, 1, 'false external change');
|
assert.equal(ext.length, 0, 'r2: no false external change');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('D2 whole seconds: no external change (control)', async (t) => {
|
test('D2 whole seconds: no external change (control)', async (t) => {
|
||||||
@@ -125,3 +125,77 @@ test('M1 one task with a 500 in missing() stalls the tick; T1 the tick after rec
|
|||||||
console.log('T1 next tick ->', await code(w.adapter.tick('demo')), 'external', w.events('task.changed.external').length, 'missing', w.events('task.missing').length);
|
console.log('T1 next tick ->', await code(w.adapter.tick('demo')), 'external', w.events('task.changed.external').length, 'missing', w.events('task.missing').length);
|
||||||
void a, b;
|
void a, b;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Round 2. P1: a create with a label and a relation whose final read fails. The fallback snapshot
|
||||||
|
// must digest the same as the next poll's read, or the poll reports the bot's create as external.
|
||||||
|
test('P1 create with a label and a relation, final read fails: snapshot matches the poll', async (t) => {
|
||||||
|
const fake = new FakeVikunja();
|
||||||
|
let armed = false;
|
||||||
|
const wrap = async (u, i = {}) => {
|
||||||
|
const res = await fake.fetch(u, i);
|
||||||
|
if (armed && (i.method ?? 'GET') === 'POST' && /\/tasks\/2\/relations$/.test(new URL(u).pathname)) {
|
||||||
|
armed = false;
|
||||||
|
fake.fault('GET', /^\/tasks\/2$/, 0);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
const w = await ready(t, { fake, fetch: wrap });
|
||||||
|
const other = await create(w);
|
||||||
|
armed = true;
|
||||||
|
const r = await create(w, { labels: [w.label], relations: [{ kind: 'related', task_ref: other.task_ref }], due_date: '2026-11-01T10:20:30.789Z' });
|
||||||
|
const s = w.snapshots(r.task_ref).at(-1);
|
||||||
|
console.log('P1 create ->', r.task_ref, 'snapshot', s?.source, 'labels', JSON.stringify(s?.fields.labels), 'due', s?.fields.due_date, 'digest==returned', s?.digest === r.digest, 'task.created', w.events('task.created').length);
|
||||||
|
await new Promise((x) => setTimeout(x, 20));
|
||||||
|
await w.adapter.tick('demo');
|
||||||
|
const ext = w.events('task.changed.external').filter((e) => e.subject === r.task_ref);
|
||||||
|
console.log('P1 poll external events for the new task', JSON.stringify(ext.map((e) => e.body.changed)));
|
||||||
|
assert.equal(ext.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// P2: a PATCH that lands but answers 500 is settled by the step's re-read; then the final read
|
||||||
|
// fails. landed counts the settled step, so the verb succeeds.
|
||||||
|
test('P2 step settled by re-read, then the final read fails: success', async (t) => {
|
||||||
|
const fake = new FakeVikunja();
|
||||||
|
let state = 0;
|
||||||
|
const wrap = async (u, i = {}) => {
|
||||||
|
const m = i.method ?? 'GET';
|
||||||
|
const p = new URL(u).pathname;
|
||||||
|
const res = await fake.fetch(u, i);
|
||||||
|
if (state === 1 && m === 'PATCH' && /\/tasks\/1$/.test(p)) state = 2;
|
||||||
|
else if (state === 2 && m === 'GET' && /\/tasks\/1$/.test(p)) {
|
||||||
|
state = 3;
|
||||||
|
fake.fault('GET', /^\/tasks\/1$/, 0);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
const w = await ready(t, { fake, fetch: wrap });
|
||||||
|
const { task_ref } = await create(w);
|
||||||
|
fake.fault('PATCH', /^\/tasks\/1$/, 500, { apply: true });
|
||||||
|
state = 1;
|
||||||
|
const c = await code(w.call(w.caps.pm, 'task.schedule', { task_ref, due_date: '2026-12-02T00:00:00.000Z' }));
|
||||||
|
console.log('P2 caller sees', c, 'state', state, 'due in tracker', fake.task(1).due, 'schedule events', w.events('task.scheduled').length, 'last snapshot', w.snapshots(task_ref).at(-1).source, w.snapshots(task_ref).at(-1).fields.due_date);
|
||||||
|
assert.equal(c, 'ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
// P3: a verb with nothing to write (the due date it already has) and a failed final read.
|
||||||
|
test('P3 nothing to write, final read fails', async (t) => {
|
||||||
|
const fake = new FakeVikunja();
|
||||||
|
let armed = false;
|
||||||
|
let gets = 0;
|
||||||
|
const wrap = async (u, i = {}) => {
|
||||||
|
const m = i.method ?? 'GET';
|
||||||
|
if (armed && m === 'GET' && /\/tasks\/1$/.test(new URL(u).pathname) && ++gets === 1) {
|
||||||
|
const res = await fake.fetch(u, i);
|
||||||
|
fake.fault('GET', /^\/tasks\/1$/, 0);
|
||||||
|
armed = false;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
return fake.fetch(u, i);
|
||||||
|
};
|
||||||
|
const w = await ready(t, { fake, fetch: wrap });
|
||||||
|
const { task_ref } = await create(w, { due_date: '2026-11-01T00:00:00.000Z' });
|
||||||
|
const patches = fake.requests.filter((x) => x.method === 'PATCH').length;
|
||||||
|
armed = true;
|
||||||
|
const c = await code(w.call(w.caps.pm, 'task.schedule', { task_ref, due_date: '2026-11-01T00:00:00.000Z' }));
|
||||||
|
console.log('P3 caller sees', c, 'PATCHes sent', fake.requests.filter((x) => x.method === 'PATCH').length - patches);
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (161.99682ms)
|
||||||
|
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (260.733424ms)
|
||||||
|
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (281.486187ms)
|
||||||
|
✔ launch events require a human CLI capability; generic emit cannot forge authority events (162.822388ms)
|
||||||
|
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (153.87044ms)
|
||||||
|
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (134.51572ms)
|
||||||
|
✔ task action subjects and linked decision trail are complete and ordered (143.892404ms)
|
||||||
|
✔ launch binding is durable and reconnecting requires the identical trusted record (91.916717ms)
|
||||||
|
✔ business isolation includes inherited object names and cross-business message references (164.453315ms)
|
||||||
|
✔ authority never transfers between action, run, target, unresolved or replaced role holder (230.37595ms)
|
||||||
|
✔ task projection uses schema current view, skipping earlier and equal-start polls (120.937737ms)
|
||||||
|
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (224.1436ms)
|
||||||
|
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (128.822107ms)
|
||||||
|
✔ both arbiters require human resolution when their cross-role route is themselves (222.91092ms)
|
||||||
|
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.440593ms)
|
||||||
|
✔ only validated broker references load; returned data and exceptions cannot expose a known token (5.2307ms)
|
||||||
|
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (4.462634ms)
|
||||||
|
✔ expiry refuses use and env references never become client data (0.735329ms)
|
||||||
|
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.490388ms)
|
||||||
|
✔ opaque tokens shorter than 16 characters refuse before use (0.343695ms)
|
||||||
|
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (2.828976ms)
|
||||||
|
✔ process reader gets own kernel identity without exposing environment values (1.590136ms)
|
||||||
|
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.916616ms)
|
||||||
|
✔ real pid 1 remains inspectable when its environment is protected (0.365469ms)
|
||||||
|
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (199.022873ms)
|
||||||
|
✔ missing and foreign-business citations refuse and roll back message and grant (194.019149ms)
|
||||||
|
✔ cross-role sends still need a matching resolved decision and consume it once (258.433362ms)
|
||||||
|
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (196.956701ms)
|
||||||
|
✔ startup token refusal returns safe code without value or partial listening broker (42.075334ms)
|
||||||
|
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (189.10371ms)
|
||||||
|
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (175.754188ms)
|
||||||
|
✔ trusted host registers later launches; socket clients never have a registration verb (175.881257ms)
|
||||||
|
✔ runtime excludes declared project roots even when host supplies no repoRoots (40.458729ms)
|
||||||
|
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (144.862401ms)
|
||||||
|
✔ v3b prototype refusals, views and append-only mutations (1024.796612ms)
|
||||||
|
✔ gated approval authorizes once, survives store reopen, and fresh approval works (244.998208ms)
|
||||||
|
✔ another run cannot consume an approval; a failed check leaves it usable (241.336493ms)
|
||||||
|
✔ two scheduled callers have exactly one grant and one consumed refusal (165.127797ms)
|
||||||
|
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (283.616106ms)
|
||||||
|
✔ class drift gated to cross-role refuses before consumption (183.3628ms)
|
||||||
|
✔ class drift cross-role to gated refuses before consumption (179.228674ms)
|
||||||
|
✔ class drift gated to within-role refuses before consumption (178.688578ms)
|
||||||
|
✔ class drift cross-role to within-role refuses before consumption (197.355563ms)
|
||||||
|
✔ class drift within-role to gated refuses before consumption (155.541641ms)
|
||||||
|
✔ class drift within-role to cross-role refuses before consumption (174.222869ms)
|
||||||
|
✔ message.send consumes approval and prevents a later send or authorize (214.551278ms)
|
||||||
|
✔ role.revoke consumes approval and prevents a later revoke or authorize (248.854374ms)
|
||||||
|
✔ creates private WAL store and excludes a second writer until explicit close (111.036837ms)
|
||||||
|
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (174.625817ms)
|
||||||
|
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (193.415895ms)
|
||||||
|
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (173.846124ms)
|
||||||
|
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (91.901517ms)
|
||||||
|
✔ async transactions refuse before invoking their function (71.450858ms)
|
||||||
|
✔ socket capability stamps launch identity; shared views use wire, no SQL client (158.003584ms)
|
||||||
|
✔ two wire claims serialize; a lost reply never automatically retries (180.553678ms)
|
||||||
|
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (128.998455ms)
|
||||||
|
✔ client preserves UTF-8 when a response divides a multibyte character (11.872033ms)
|
||||||
|
✔ committed mutation followed by dropped reply reports unknown and is never retried (144.718933ms)
|
||||||
|
ℹ tests 58
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 58
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2568.827198
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
✔ config directory and file path follow MOSAIC_CONFIG (2.210719ms)
|
||||||
|
✔ the fixture business validates and comes back frozen (8.770712ms)
|
||||||
|
✔ two instances may share a definition (2.177367ms)
|
||||||
|
✔ top-level refusals (8.827406ms)
|
||||||
|
✔ arbiters and projects (14.50992ms)
|
||||||
|
✔ role instances (4.839355ms)
|
||||||
|
✔ Vikunja bots (11.538611ms)
|
||||||
|
✔ a role without Vikunja takes no tracker block (3.793442ms)
|
||||||
|
✔ credential references match the definition's services (4.868786ms)
|
||||||
|
✔ launch (14.960448ms)
|
||||||
|
✔ loadBusiness: file checks (3.236296ms)
|
||||||
|
✔ loadBusiness: not a regular file (91.08637ms)
|
||||||
|
✔ loading writes nothing (2.57653ms)
|
||||||
|
✔ names that are Object.prototype properties don't count as declared (6.890756ms)
|
||||||
|
✔ the shipped example refuses as written and validates once filled in (2.883122ms)
|
||||||
|
✔ usage errors exit 4 (869.470778ms)
|
||||||
|
✔ validate: a good business exits 0 and prints instance digests (82.042246ms)
|
||||||
|
✔ validate: project files (417.1794ms)
|
||||||
|
✔ validate: missing files and a broken system config (390.376836ms)
|
||||||
|
✔ validate: credential reference problems exit 2 and name each one (99.3234ms)
|
||||||
|
✔ validate: a token file inside the repository is refused (103.756822ms)
|
||||||
|
✔ validate: role definitions come from MOSAIC_ROLES_DIR (234.927357ms)
|
||||||
|
✔ resolve: prints one instance's record (281.877257ms)
|
||||||
|
✔ resolve: refusals (989.310152ms)
|
||||||
|
✔ parse: exactly one of file or env, plus the service's date (4.549447ms)
|
||||||
|
✔ check: a good file has no problems (1.209331ms)
|
||||||
|
✔ check never opens the file: a write-only token passes (0.804481ms)
|
||||||
|
✔ check: file problems (1.630366ms)
|
||||||
|
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (1.247451ms)
|
||||||
|
✔ check: dates and environment references (0.70132ms)
|
||||||
|
✔ path and load (3.972788ms)
|
||||||
|
✔ refusals (2.078689ms)
|
||||||
|
✔ systemVars flattens the validated config (3.4031ms)
|
||||||
|
✔ precedence: system, business, project, project role, agent (9.73379ms)
|
||||||
|
✔ limits narrow the definition and never widen it (5.213511ms)
|
||||||
|
✔ role.launch stays within-role only for the instance the launch block names (9.175749ms)
|
||||||
|
✔ limits.authority without role.launch leaves the launcher with no launch block (3.165563ms)
|
||||||
|
✔ limits.authority narrows cross-role actions too (2.155587ms)
|
||||||
|
✔ classify (2.074589ms)
|
||||||
|
✔ the record carries what the broker and launcher need (1.773542ms)
|
||||||
|
✔ digest: key order doesn't matter, any value change does (11.515402ms)
|
||||||
|
✔ refusals (3.850074ms)
|
||||||
|
✔ the four shipped version 2 roles load (5.81714ms)
|
||||||
|
✔ shipped role scopes match addendum B section 2 and the SR runbook (2.413725ms)
|
||||||
|
✔ shipped authority follows the note's table (1.528904ms)
|
||||||
|
✔ version 1 files keep loading with no authority (1.516579ms)
|
||||||
|
✔ the conductor policy isn't a role (0.505683ms)
|
||||||
|
✔ a missing role file is exit 4, a symbolic link too (0.688851ms)
|
||||||
|
✔ version 2 refusals (2.788584ms)
|
||||||
|
✔ authority: closed vocabulary, no gated-only action, no overlap (3.696945ms)
|
||||||
|
✔ credentials: Gitea scopes (1.798267ms)
|
||||||
|
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.959897ms)
|
||||||
|
✔ credentials: services (0.894557ms)
|
||||||
|
✔ contract: a non-empty regular Markdown file beside the role file (1.458286ms)
|
||||||
|
✔ every key names known layers and a merge rule (2.305419ms)
|
||||||
|
✔ unknown keys and wrong layers refuse (1.12706ms)
|
||||||
|
✔ types (2.631133ms)
|
||||||
|
✔ merge: defaults, then the most specific layer wins (0.412703ms)
|
||||||
|
✔ merge: limits only narrow, and provenance lists each source (0.911658ms)
|
||||||
|
✔ merge doesn't change its inputs (0.224637ms)
|
||||||
|
ℹ tests 60
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 60
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 3610.609773
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (34.487862ms)
|
||||||
|
✔ approvals: the ledger is appended and folded into open requests with bind and approval states (14.074347ms)
|
||||||
|
✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (6.773058ms)
|
||||||
|
✔ approvals: a button approves only on its own request message with the matching custom id (12.400304ms)
|
||||||
|
✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (43.608983ms)
|
||||||
|
✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (22.126058ms)
|
||||||
|
✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (27.270723ms)
|
||||||
|
✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (13.726781ms)
|
||||||
|
✔ authorize: open channel, listed user (17.885431ms)
|
||||||
|
✔ authorize: wrong guild (0.292072ms)
|
||||||
|
✔ authorize: no guild (DM) (0.202845ms)
|
||||||
|
✔ authorize: unlisted channel (3.905942ms)
|
||||||
|
✔ authorize: unknown channel, no info (0.210231ms)
|
||||||
|
✔ authorize: thread of listed parent (0.248472ms)
|
||||||
|
✔ authorize: thread of unlisted parent (0.177088ms)
|
||||||
|
✔ authorize: text channel that is not a thread and not listed (0.190345ms)
|
||||||
|
✔ authorize: unlisted user (0.448978ms)
|
||||||
|
✔ authorize: no author (0.333759ms)
|
||||||
|
✔ authorize: bot author (listed id, bot flag) (0.164449ms)
|
||||||
|
✔ authorize: system author (0.153064ms)
|
||||||
|
✔ authorize: the bot itself (0.111149ms)
|
||||||
|
✔ authorize: webhook (0.122779ms)
|
||||||
|
✔ authorize: mention channel without mention (3.295562ms)
|
||||||
|
✔ authorize: mention channel with bot mention (0.193551ms)
|
||||||
|
✔ authorize: mention channel with @everyone only (0.143018ms)
|
||||||
|
✔ authorize: mention channel mentioning someone else (0.097916ms)
|
||||||
|
✔ authorize: mention channel, content says @bot but mentions empty (0.127677ms)
|
||||||
|
✔ authorize: private thread under mention channel, mentioned (0.108796ms)
|
||||||
|
✔ authorize: private thread under mention channel, not mentioned (0.093467ms)
|
||||||
|
✔ authorize: thread in another guild per channel info (3.951563ms)
|
||||||
|
✔ authorize: not an object (0.132099ms)
|
||||||
|
✔ authorize: no id (0.096086ms)
|
||||||
|
✔ authorize: oversize content is accepted and flagged (0.0958ms)
|
||||||
|
✔ authorize: exactly the limit is not oversize (0.075696ms)
|
||||||
|
✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (6.783025ms)
|
||||||
|
✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (4.049927ms)
|
||||||
|
✔ binding: a complete binding validates and is frozen (3.220184ms)
|
||||||
|
✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.45178ms)
|
||||||
|
✔ binding: empty allowlists refuse (0.424632ms)
|
||||||
|
✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.460828ms)
|
||||||
|
✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.863865ms)
|
||||||
|
✔ binding: file must be 0600, regular, not a symlink (4.287306ms)
|
||||||
|
✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (3.011523ms)
|
||||||
|
✔ cli: check refuses a non-0600 token file with exit 2 before any network use (217.82549ms)
|
||||||
|
✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (15.349608ms)
|
||||||
|
✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (1080.69582ms)
|
||||||
|
✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (307.79078ms)
|
||||||
|
✔ cli: run refuses when STOP is present, before any network use (172.208585ms)
|
||||||
|
✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.897057ms)
|
||||||
|
✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.342074ms)
|
||||||
|
✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (81.604347ms)
|
||||||
|
✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (84.640489ms)
|
||||||
|
✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.998392ms)
|
||||||
|
✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (2.084332ms)
|
||||||
|
✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.962625ms)
|
||||||
|
✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (7.880217ms)
|
||||||
|
✔ turn: a second message during a turn is held by the engine, both get their own reply and record (37.152437ms)
|
||||||
|
✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (7.685887ms)
|
||||||
|
✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.67433ms)
|
||||||
|
✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (2.167776ms)
|
||||||
|
✔ restart: an inbox with three ids and a replay of the same three produces zero turns (43.464381ms)
|
||||||
|
✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (34.229383ms)
|
||||||
|
✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (30.796295ms)
|
||||||
|
✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (32.221013ms)
|
||||||
|
✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.485179ms)
|
||||||
|
✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (14.687224ms)
|
||||||
|
✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (29.243855ms)
|
||||||
|
✔ journal: no token-shaped string and no model output on the drop path reaches disk (11.767537ms)
|
||||||
|
✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (5.128386ms)
|
||||||
|
✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (8.727482ms)
|
||||||
|
✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (26.132094ms)
|
||||||
|
✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.494167ms)
|
||||||
|
✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (7.685619ms)
|
||||||
|
✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.833757ms)
|
||||||
|
✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (3.939416ms)
|
||||||
|
✔ context: a writable root adds the write rules and says a write is real only once Jason commits (3.074028ms)
|
||||||
|
✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.362179ms)
|
||||||
|
✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (4.817406ms)
|
||||||
|
✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (3.082078ms)
|
||||||
|
✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.867042ms)
|
||||||
|
✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (3.319401ms)
|
||||||
|
✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.48708ms)
|
||||||
|
✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (114.787619ms)
|
||||||
|
✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (96.012621ms)
|
||||||
|
✔ engine: one prompt, one turn, text and usage come back (117.267651ms)
|
||||||
|
✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (385.920468ms)
|
||||||
|
✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (307.654865ms)
|
||||||
|
✔ engine: timeout sends abort and fails only that turn; the process stays (146.711305ms)
|
||||||
|
✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (299.092131ms)
|
||||||
|
✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (166.825666ms)
|
||||||
|
✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (213.598765ms)
|
||||||
|
✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (616.243685ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (9.382248ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (1.804695ms)
|
||||||
|
✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (455.497693ms)
|
||||||
|
✔ engine: a malformed JSONL line fails the turn, not the process (33.520961ms)
|
||||||
|
✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (54.1439ms)
|
||||||
|
✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (3.468422ms)
|
||||||
|
✔ gateway: missed ack closes the socket and resumes with the last sequence (6.755832ms)
|
||||||
|
✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (4.39287ms)
|
||||||
|
✔ gateway: op 9 resumable resumes (0.558088ms)
|
||||||
|
✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (6.634387ms)
|
||||||
|
✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.663017ms)
|
||||||
|
✔ gateway: close() is final and unparseable frames are ignored (0.45109ms)
|
||||||
|
✔ git: config validation is strict, needs write: true, a work tree and a private token file (180.855233ms)
|
||||||
|
✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (195.087231ms)
|
||||||
|
✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (331.606799ms)
|
||||||
|
✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.516461ms)
|
||||||
|
✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (376.392144ms)
|
||||||
|
✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (190.005319ms)
|
||||||
|
✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (181.910749ms)
|
||||||
|
✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (281.704081ms)
|
||||||
|
✔ git: push pushes the named branch only and reports up to date (74.740746ms)
|
||||||
|
✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (94.007326ms)
|
||||||
|
✔ git: the credential helper answers get over https from a private file and nothing else (376.924827ms)
|
||||||
|
✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (1001.713929ms)
|
||||||
|
✔ lock: the claim is exclusive; a second start against a live owner refuses (5.634528ms)
|
||||||
|
✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (35.483959ms)
|
||||||
|
✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (6.841195ms)
|
||||||
|
✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (3.49952ms)
|
||||||
|
✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (194.544821ms)
|
||||||
|
✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (1035.119501ms)
|
||||||
|
✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.661416ms)
|
||||||
|
✔ lock: a process whose start marker or boot id cannot be read refuses to claim (0.807626ms)
|
||||||
|
✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.801628ms)
|
||||||
|
✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (69.853531ms)
|
||||||
|
✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (179.73331ms)
|
||||||
|
✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (91.055965ms)
|
||||||
|
✔ notices: a kind is recorded per UTC day and found again (0.736817ms)
|
||||||
|
✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (107.432405ms)
|
||||||
|
✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (132.449148ms)
|
||||||
|
✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (131.450452ms)
|
||||||
|
✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (274.897047ms)
|
||||||
|
✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (1279.582667ms)
|
||||||
|
✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.874588ms)
|
||||||
|
✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.848359ms)
|
||||||
|
✔ rest: content and nonce limits are enforced locally; typing never throws (0.849946ms)
|
||||||
|
✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.861657ms)
|
||||||
|
✔ setspark config: a bare https or loopback origin, a private key file, a principal (27.493778ms)
|
||||||
|
✔ setspark config: reaches the tools config and the binding as a fixed key (13.504085ms)
|
||||||
|
✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (3.85414ms)
|
||||||
|
✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (4.34759ms)
|
||||||
|
✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (38.399576ms)
|
||||||
|
✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (143.161052ms)
|
||||||
|
✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (14.455155ms)
|
||||||
|
✔ setspark keys: read per call, one printable token per file, rotation without a restart (7.034683ms)
|
||||||
|
✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (1.032953ms)
|
||||||
|
✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (5.837698ms)
|
||||||
|
✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1019.925443ms)
|
||||||
|
✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.838506ms)
|
||||||
|
✔ setspark verbs: writes carry the turn's key and the asserted requester, reads carry no key, and the api key never appears in text or details (11.626826ms)
|
||||||
|
✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (2.453047ms)
|
||||||
|
✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.406606ms)
|
||||||
|
✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (4.624686ms)
|
||||||
|
✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.093436ms)
|
||||||
|
✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.352767ms)
|
||||||
|
✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (7.855786ms)
|
||||||
|
✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.903809ms)
|
||||||
|
✔ tools: listing and search caps hold (47.173174ms)
|
||||||
|
✔ tools: credential shapes are caught; ordinary prose and ids are not (2.037206ms)
|
||||||
|
✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (27.565853ms)
|
||||||
|
✔ tools: an unreadable file under the root is skipped by search and refused by read (7.725685ms)
|
||||||
|
✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (8.025072ms)
|
||||||
|
✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (16.110027ms)
|
||||||
|
✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.875058ms)
|
||||||
|
✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (4.361211ms)
|
||||||
|
✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (4.935673ms)
|
||||||
|
✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (3.021393ms)
|
||||||
|
✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1100.072164ms)
|
||||||
|
✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (9.979096ms)
|
||||||
|
✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.645954ms)
|
||||||
|
✔ web: web_search asks the instance for json, returns at most ten clean results, and refuses a bad query, a down instance or an unusable answer (7.484765ms)
|
||||||
|
✔ web: the tool set enables the web tools only with a web key, counts them in the budget, and records url, status and hits (5.271081ms)
|
||||||
|
ℹ tests 173
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 173
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 3853.394207
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
OK status with missing harness credential exits 3 and still lists accounts
|
||||||
|
OK status reports harness credential (read-only) + mosaic accounts
|
||||||
|
OK api key material never reaches output
|
||||||
|
OK oauth token material never reaches output
|
||||||
|
OK unparseable credential file exits 2
|
||||||
|
OK symlinked credential file exits 4
|
||||||
|
OK env-side credential names reported
|
||||||
|
OK env var values never reach output
|
||||||
|
OK accounts without an accounts dir reports none and creates nothing
|
||||||
|
OK accounts lists files and marks the active one
|
||||||
|
OK loose account perms flagged in listing
|
||||||
|
OK agent --auth with missing account file refuses (exit 4)
|
||||||
|
OK agent --auth with non-0600 account file refuses
|
||||||
|
OK agent --auth with invalid account name refuses
|
||||||
|
OK auth.sh without valid config refuses
|
||||||
|
|
||||||
|
selftest: 15 passed, 0 failed
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
Note: switching to 'c9ef7ee49f0785287de7ff41130dd4761ad2c7e3'.
|
||||||
|
|
||||||
|
You are in 'detached HEAD' state. You can look around, make experimental
|
||||||
|
changes and commit them, and you can discard any commits you make in this
|
||||||
|
state without impacting any branches by switching back to a branch.
|
||||||
|
|
||||||
|
If you want to create a new branch to retain commits you create, you may
|
||||||
|
do so (now or later) by using -c with the switch command. Example:
|
||||||
|
|
||||||
|
git switch -c <new-branch-name>
|
||||||
|
|
||||||
|
Or undo this operation with:
|
||||||
|
|
||||||
|
git switch -
|
||||||
|
|
||||||
|
Turn off this advice by setting config variable advice.detachedHead to false
|
||||||
|
|
||||||
|
Not currently on any branch.
|
||||||
|
nothing to commit, working tree clean
|
||||||
|
Note: switching to 'c9ef7ee49f0785287de7ff41130dd4761ad2c7e3'.
|
||||||
|
|
||||||
|
You are in 'detached HEAD' state. You can look around, make experimental
|
||||||
|
changes and commit them, and you can discard any commits you make in this
|
||||||
|
state without impacting any branches by switching back to a branch.
|
||||||
|
|
||||||
|
If you want to create a new branch to retain commits you create, you may
|
||||||
|
do so (now or later) by using -c with the switch command. Example:
|
||||||
|
|
||||||
|
git switch -c <new-branch-name>
|
||||||
|
|
||||||
|
Or undo this operation with:
|
||||||
|
|
||||||
|
git switch -
|
||||||
|
|
||||||
|
Turn off this advice by setting config variable advice.detachedHead to false
|
||||||
|
|
||||||
|
OK dry-run: allowed change, exit 0, nothing committed (exit 0)
|
||||||
|
OK dry-run committed nothing
|
||||||
|
OK apply: allowed change exits 0 (exit 0)
|
||||||
|
OK apply: attribution in commit subject
|
||||||
|
OK apply: target tree clean after commit
|
||||||
|
OK disallowed path refused (exit 1)
|
||||||
|
OK disallowed path: target untouched
|
||||||
|
OK syntax gate refused broken .mjs (exit 1)
|
||||||
|
OK syntax gate: target untouched
|
||||||
|
OK suite failure refused (exit 1)
|
||||||
|
OK suite failure: target reverted to clean
|
||||||
|
OK disabled policy refused (exit 2)
|
||||||
|
OK disabled policy: target untouched
|
||||||
|
OK failed run refused (exit 1)
|
||||||
|
OK failed run: target untouched
|
||||||
|
OK missing run exits 4 (exit 4)
|
||||||
|
OK invalid policy exits 2 (exit 2)
|
||||||
|
|
||||||
|
selftest: 17 passed, 0 failed
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
OK absent adapter defaults to pi
|
||||||
|
OK adapter mock validates (exit 0)
|
||||||
|
OK unsupported adapter exits 2 (exit 2)
|
||||||
|
OK env exports adapter
|
||||||
|
OK bootstrap creates default when absent (exit 0)
|
||||||
|
OK bootstrap wrote config file
|
||||||
|
OK bootstrap is idempotent on existing config (exit 0)
|
||||||
|
OK bootstrap did not rewrite existing config
|
||||||
|
OK validate missing config exits 3 (exit 3)
|
||||||
|
OK malformed JSON exits 2 (exit 2)
|
||||||
|
OK unsupported configVersion exits 2 (exit 2)
|
||||||
|
OK unknown top-level key exits 2 (exit 2)
|
||||||
|
OK unknown execution key exits 2 (exit 2)
|
||||||
|
OK unsupported backend exits 2 (exit 2)
|
||||||
|
OK unsupported environment exits 2 (exit 2)
|
||||||
|
OK relative dataRoot exits 2 (exit 2)
|
||||||
|
OK non-canonical dataRoot exits 2 (exit 2)
|
||||||
|
OK filesystem root dataRoot exits 2 (exit 2)
|
||||||
|
OK home directory dataRoot exits 2 (exit 2)
|
||||||
|
OK dataRoot containing config dir exits 2 (exit 2)
|
||||||
|
OK control character in provider exits 2 (exit 2)
|
||||||
|
OK symlinked config file exits 2 (exit 2)
|
||||||
|
OK env exports resolve correctly
|
||||||
|
OK failed validation modified nothing
|
||||||
|
|
||||||
|
selftest: 24 passed, 0 failed
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
toolchain: node v26.8.1
|
||||||
|
|
||||||
|
OK syntax: packages/discord/src/approvals.mjs
|
||||||
|
OK syntax: packages/discord/src/authorize.mjs
|
||||||
|
OK syntax: packages/discord/src/binding.mjs
|
||||||
|
OK syntax: packages/discord/src/cli.mjs
|
||||||
|
OK syntax: packages/discord/src/connector.mjs
|
||||||
|
OK syntax: packages/discord/src/context.mjs
|
||||||
|
OK syntax: packages/discord/src/engine-pi.mjs
|
||||||
|
OK syntax: packages/discord/src/errors.mjs
|
||||||
|
OK syntax: packages/discord/src/gateway.mjs
|
||||||
|
OK syntax: packages/discord/src/git.mjs
|
||||||
|
OK syntax: packages/discord/src/journal.mjs
|
||||||
|
OK syntax: packages/discord/src/rest.mjs
|
||||||
|
OK syntax: packages/discord/src/setspark.mjs
|
||||||
|
OK syntax: packages/discord/src/tools.mjs
|
||||||
|
OK syntax: packages/discord/src/web.mjs
|
||||||
|
OK syntax: packages/discord/bin/git-credential.mjs
|
||||||
|
OK syntax: packages/discord/extension/tools.mjs
|
||||||
|
OK syntax: packages/discord/tests/approvals.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/authorize.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/binding.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/connector.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/context.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/engine.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/fake-pi.mjs
|
||||||
|
OK syntax: packages/discord/tests/gateway.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/git.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/helpers.mjs
|
||||||
|
OK syntax: packages/discord/tests/journal.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/recover.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/rest.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/setspark.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/tools.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/web.test.mjs
|
||||||
|
OK syntax: packages/discord/fixtures/claim-worker.mjs
|
||||||
|
OK syntax: packages/discord/fixtures/legacy-owner-worker.mjs
|
||||||
|
OK syntax: scripts/discord.sh
|
||||||
|
OK syntax: scripts/discord-service.sh
|
||||||
|
OK packages/discord declares no dependencies
|
||||||
|
OK no bot-token-shaped string in packages/discord
|
||||||
|
OK fixture binding uses placeholder ids only
|
||||||
|
OK fixture binding validates
|
||||||
|
OK real pi with the extension exposes exactly list_dir, read_file, search and no built-in tool
|
||||||
|
OK real pi with a writable root exposes exactly the three reads plus write_file and edit_file, and writes nothing at start
|
||||||
|
OK real pi with a web key exposes the three reads plus web_fetch and web_search, and no write tool without a writable root
|
||||||
|
OK real pi with a git root exposes the reads, writes and the four git verbs, commits nothing at start, and never shows the token
|
||||||
|
OK real pi with protocol vault adds reserve_id to the git verbs
|
||||||
|
OK real pi with a setspark key exposes the reads and the eight record verbs, no counters, and never shows the key
|
||||||
|
OK real pi refuses a git key on a read-only root (fail closed)
|
||||||
|
OK real pi with the pilot flags (--no-tools) exposes no tool at all
|
||||||
|
OK real pi exits non-zero without MOSAIC_DISCORD_TOOLS: no session, no tools (fail closed)
|
||||||
|
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
|
||||||
|
OK node --test packages/discord/tests/ (ℹ pass 173)
|
||||||
|
OK scripts/discord.sh --help exits 0
|
||||||
|
OK scripts/discord.sh check without a binding exits 4
|
||||||
|
OK scripts/discord.sh recover without a binding exits 4
|
||||||
|
OK scripts/discord.sh reload without a binding exits 4
|
||||||
|
OK scripts/discord-service.sh without a command exits 4
|
||||||
|
OK service unit renders with the repository path, a supervised run as the main process, exit 3 never retried, and reload as SIGHUP
|
||||||
|
OK service install writes the rendered unit (0644) and leaves no temp file
|
||||||
|
OK service install a second time reports unchanged
|
||||||
|
OK systemd-analyze verify accepts the rendered unit
|
||||||
|
OK service uninstall removes the unit file
|
||||||
|
OK service install with an unknown flag exits 4
|
||||||
|
OK service install with USER unset finishes and names the account for lingering
|
||||||
|
|
||||||
|
discord suite: 64 passed, 0 failed
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
OK initial ordinary-file install
|
||||||
|
OK installed tree matches canonical source
|
||||||
|
OK installed tree has no symlinks
|
||||||
|
OK check detects installation drift
|
||||||
|
OK sync refuses to overwrite installation drift
|
||||||
|
OK check detects an extra destination file
|
||||||
|
OK check detects an extra destination directory
|
||||||
|
OK check rejects a destination symlink
|
||||||
|
OK sync accepts a canonical source update
|
||||||
|
OK updated installation matches canonical source
|
||||||
|
scripts/test-extension-package.sh: line 14: 3895996 Killed "$@" > /dev/null 2>&1
|
||||||
|
OK forced interruption kills the replacing process
|
||||||
|
OK next invocation recovers old consistent installation
|
||||||
|
OK interrupted replacement rolled back
|
||||||
|
OK sync succeeds after interruption recovery
|
||||||
|
OK unlocked stale lock file does not block
|
||||||
|
OK active lock refuses a concurrent sync
|
||||||
|
OK source symlink fails closed
|
||||||
|
OK nested second entrypoint fails closed
|
||||||
|
|
||||||
|
extension package selftest: 18 passed, 0 failed
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
toolchain: node v26.8.1, python 3.12.8, jsonschema 4.26.0
|
||||||
|
|
||||||
|
OK syntax: scripts/foundation-inspect.mjs
|
||||||
|
OK syntax: scripts/foundation/strict-json.mjs
|
||||||
|
OK syntax: scripts/foundation/canonical.mjs
|
||||||
|
OK syntax: scripts/foundation/resolve.mjs
|
||||||
|
OK syntax: scripts/foundation/validate-record.mjs
|
||||||
|
OK syntax: scripts/foundation/fixtures/build-fixtures.mjs
|
||||||
|
OK syntax: scripts/foundation/canonical.test.mjs
|
||||||
|
OK syntax: scripts/foundation/cli.test.mjs
|
||||||
|
OK syntax: scripts/foundation/fixtures.test.mjs
|
||||||
|
OK syntax: scripts/foundation/resolve.test.mjs
|
||||||
|
OK syntax: scripts/foundation/strict-json.test.mjs
|
||||||
|
OK syntax: scripts/foundation/verify-schema.py (ast only; no bytecode written)
|
||||||
|
OK fixture generator runs
|
||||||
|
OK checked-in fixtures/bundles equal a fresh generation
|
||||||
|
OK checked-in fixtures/raw equal a fresh generation
|
||||||
|
OK checked-in fixtures/index.json equal a fresh generation
|
||||||
|
OK checked-in demo bundles equal a fresh generation
|
||||||
|
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
|
||||||
|
OK node --test scripts/foundation/ (ℹ pass 80)
|
||||||
|
OK differential schema oracle: PASS: differential schema oracle (finite corpus; compatibility evidence, not equivalence proof)
|
||||||
|
platform witness: strftime('%Y') for year 999 -> '999' (pinned checker refuses years 0001..0999)
|
||||||
|
node v26.8.1; corpus 1568 records (38 pinned fixtures, 478 unique bundle records, 1052 typeCase/mutation/lexical cases)
|
||||||
|
schema column: agree-valid 540, agree-invalid 991, DISAGREEMENTS 0; strict-only (parser-bound) cases: 27; unsupported-kind records not schema-assessed by the inspector: 10
|
||||||
|
profile column (schema-valid records only): profile-valid 510, profile-invalid 30
|
||||||
|
profile refusals asserted: 30 schema-agreed-valid records refused only by the strict typed-string profile (rule profile-pattern-mismatch), 12 declared by name; 73 named probes verified against declared schema/profile columns
|
||||||
|
OK oracle: zero schema-column disagreements with the pinned checker
|
||||||
|
OK oracle: strict-only profile refusals are counted and asserted
|
||||||
|
OK demo: permitted read preview exits 0 (exit 0)
|
||||||
|
OK demo: permitted file.change preview exits 0 (exit 0)
|
||||||
|
OK demo: assignment.change proposal is unresolved (exit 3) (exit 3)
|
||||||
|
OK demo: revoked registration is refused (exit 3) (exit 3)
|
||||||
|
OK demo: message is not authority (exit 3) (exit 3)
|
||||||
|
OK usage: no arguments exits 2 (exit 2)
|
||||||
|
OK io: missing file exits 4 (exit 4)
|
||||||
|
OK io: directory exits 4 (exit 4)
|
||||||
|
OK io: symlink exits 4 (O_NOFOLLOW) (exit 4)
|
||||||
|
OK bound: oversize fixture exits 2 (exit 2)
|
||||||
|
OK profile: one final LF in a typed selection id is refused before admission (exit 2) (exit 2)
|
||||||
|
OK profile: two final LFs fail the schema pattern itself (exit 2) (exit 2)
|
||||||
|
OK profile: escaped newlines in free-form text stay allowed (exit 0) (exit 0)
|
||||||
|
OK profile refusal is invalid-request/profile-pattern-mismatch with selection and operation withheld, value not echoed
|
||||||
|
OK text output starts with the disclaimer
|
||||||
|
OK json output is valid JSON with result allowed and exactly the charter §7 fields
|
||||||
|
OK json golden matches byte-for-byte
|
||||||
|
OK sandboxed bundle run (env -i, PATH=/nonexistent) produced the unresolved proposal
|
||||||
|
OK sandbox inventory (path/type/size/mode/uid/gid/inode/mtime/sha256) unchanged by runs
|
||||||
|
OK canary never printed (bundle run and credential-file run)
|
||||||
|
OK a non-bundle JSON file is refused at the shape gate, not read into output
|
||||||
|
OK no field of the non-bundle file is echoed
|
||||||
|
|
||||||
|
selftest: 44 passed, 0 failed
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
toolchain: node v26.8.1, git version 2.55.0
|
||||||
|
|
||||||
|
OK syntax: packages/queue/src/cli.mjs
|
||||||
|
OK syntax: packages/queue/src/errors.mjs
|
||||||
|
OK syntax: packages/queue/src/io.mjs
|
||||||
|
OK syntax: packages/queue/src/lock.mjs
|
||||||
|
OK syntax: packages/queue/src/queue.mjs
|
||||||
|
OK syntax: packages/queue/src/review.mjs
|
||||||
|
OK syntax: packages/queue/src/store.mjs
|
||||||
|
OK syntax: packages/queue/tests/commit.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/data.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/dispatch.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/helpers.mjs
|
||||||
|
OK syntax: packages/queue/tests/lock.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/migration.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/review.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/store.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/write.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/fixtures/fake-gitea.mjs
|
||||||
|
OK syntax: packages/queue/tests/fixtures/kill-at.mjs
|
||||||
|
OK syntax: packages/queue/tests/fixtures/lock-child.mjs
|
||||||
|
OK syntax: packages/queue/tests/fixtures/mosaic-pre-a2.sh
|
||||||
|
OK syntax: scripts/queue-commit.sh
|
||||||
|
OK syntax: scripts/git-hooks/pre-commit
|
||||||
|
OK syntax: scripts/mosaic
|
||||||
|
OK queue-commit.sh, the guard and scripts/mosaic are executable
|
||||||
|
OK packages/queue declares no dependencies
|
||||||
|
ℹ tests 148
|
||||||
|
ℹ pass 148
|
||||||
|
ℹ fail 0
|
||||||
|
OK node --test packages/queue/tests/
|
||||||
|
OK scripts/mosaic queue help
|
||||||
|
skip queue verify and render --check: this checkout (/home/jwoltje/filbert-scratch/r38b/base) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
|
||||||
|
|
||||||
|
queue suite: 27 passed, 0 failed
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
OK valid RELEASE resolves (exit 0)
|
||||||
|
OK invalid RELEASE exits 1 (exit 1)
|
||||||
|
OK missing RELEASE exits 1 (exit 1)
|
||||||
|
OK valid RELEASE leaves image tag consistent with version
|
||||||
|
skip state-machine cases (docker daemon unavailable)
|
||||||
|
|
||||||
|
selftest: 4 passed, 0 failed
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
OK valid task validates (exit 0)
|
||||||
|
OK unknown task key exits 2 (exit 2)
|
||||||
|
OK unsupported taskVersion exits 2 (exit 2)
|
||||||
|
OK invalid task id exits 2 (exit 2)
|
||||||
|
OK empty prompt exits 2 (exit 2)
|
||||||
|
OK NUL in expectExact exits 2 (exit 2)
|
||||||
|
OK out-of-range timeout exits 2 (exit 2)
|
||||||
|
OK missing mission file exits 4 (exit 4)
|
||||||
|
OK task with valid mission validates (exit 0)
|
||||||
|
OK invalid mission exits 2 (exit 2)
|
||||||
|
OK validate missing task exits 4 (exit 4)
|
||||||
|
OK validation does not modify the task file
|
||||||
|
OK prune dry-run exits 0 (exit 0)
|
||||||
|
OK dry-run deleted nothing
|
||||||
|
OK prune --keep=2 --yes removes oldest (exit 0)
|
||||||
|
OK kept exactly 2 newest runs
|
||||||
|
OK newest run kept, oldest pruned
|
||||||
|
OK append-only receipt written (3 entries)
|
||||||
|
OK sessions/workspaces untouched by prune
|
||||||
|
OK prune with invalid keep exits 4 (exit 4)
|
||||||
|
skip adapter seam cases (docker daemon unavailable)
|
||||||
|
skip workspace/capability cases (docker daemon unavailable)
|
||||||
|
skip live task cases (docker unavailable)
|
||||||
|
OK onboard without name exits 4 (non-interactive) (exit 4)
|
||||||
|
OK onboard --name renders profile (exit 0)
|
||||||
|
OK profile written
|
||||||
|
OK canon structure: required filled, optional placeholdered
|
||||||
|
OK canon sections present
|
||||||
|
FAIL user recall run succeeds (exit 1)
|
||||||
|
FAIL recalled user name (response: )
|
||||||
|
OK no agent identity on headless run
|
||||||
|
|
||||||
|
selftest: 26 passed, 2 failed
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (243.688933ms)
|
||||||
|
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (394.370236ms)
|
||||||
|
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (378.259124ms)
|
||||||
|
✔ launch events require a human CLI capability; generic emit cannot forge authority events (190.030177ms)
|
||||||
|
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (166.414908ms)
|
||||||
|
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (196.029388ms)
|
||||||
|
✔ task action subjects and linked decision trail are complete and ordered (271.039591ms)
|
||||||
|
✔ launch binding is durable and reconnecting requires the identical trusted record (105.692018ms)
|
||||||
|
✔ business isolation includes inherited object names and cross-business message references (299.44263ms)
|
||||||
|
✔ authority never transfers between action, run, target, unresolved or replaced role holder (336.471786ms)
|
||||||
|
✔ task projection uses schema current view, skipping earlier and equal-start polls (116.7184ms)
|
||||||
|
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (212.071404ms)
|
||||||
|
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (170.936558ms)
|
||||||
|
✔ both arbiters require human resolution when their cross-role route is themselves (230.085428ms)
|
||||||
|
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.483051ms)
|
||||||
|
✔ only validated broker references load; returned data and exceptions cannot expose a known token (8.861539ms)
|
||||||
|
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (2.423716ms)
|
||||||
|
✔ expiry refuses use and env references never become client data (0.639165ms)
|
||||||
|
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.507093ms)
|
||||||
|
✔ opaque tokens shorter than 16 characters refuse before use (0.344056ms)
|
||||||
|
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (2.87ms)
|
||||||
|
✔ process reader gets own kernel identity without exposing environment values (1.745649ms)
|
||||||
|
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (1.199018ms)
|
||||||
|
✔ real pid 1 remains inspectable when its environment is protected (0.454652ms)
|
||||||
|
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (284.772283ms)
|
||||||
|
✔ missing and foreign-business citations refuse and roll back message and grant (284.754519ms)
|
||||||
|
✔ cross-role sends still need a matching resolved decision and consume it once (363.897447ms)
|
||||||
|
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (267.365284ms)
|
||||||
|
✔ startup token refusal returns safe code without value or partial listening broker (46.571245ms)
|
||||||
|
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (259.065468ms)
|
||||||
|
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (239.167291ms)
|
||||||
|
✔ trusted host registers later launches; socket clients never have a registration verb (198.566643ms)
|
||||||
|
✔ runtime excludes declared project roots even when host supplies no repoRoots (36.014187ms)
|
||||||
|
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (191.712893ms)
|
||||||
|
✔ v3b prototype refusals, views and append-only mutations (1386.640134ms)
|
||||||
|
✔ gated approval authorizes once, survives store reopen, and fresh approval works (350.424385ms)
|
||||||
|
✔ another run cannot consume an approval; a failed check leaves it usable (346.173632ms)
|
||||||
|
✔ two scheduled callers have exactly one grant and one consumed refusal (237.542781ms)
|
||||||
|
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (353.298622ms)
|
||||||
|
✔ class drift gated to cross-role refuses before consumption (253.337442ms)
|
||||||
|
✔ class drift cross-role to gated refuses before consumption (323.574906ms)
|
||||||
|
✔ class drift gated to within-role refuses before consumption (319.086942ms)
|
||||||
|
✔ class drift cross-role to within-role refuses before consumption (293.421268ms)
|
||||||
|
✔ class drift within-role to gated refuses before consumption (200.739612ms)
|
||||||
|
✔ class drift within-role to cross-role refuses before consumption (190.277177ms)
|
||||||
|
✔ message.send consumes approval and prevents a later send or authorize (228.418852ms)
|
||||||
|
✔ role.revoke consumes approval and prevents a later revoke or authorize (248.932208ms)
|
||||||
|
✔ creates private WAL store and excludes a second writer until explicit close (198.246349ms)
|
||||||
|
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (220.628917ms)
|
||||||
|
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (284.709518ms)
|
||||||
|
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (209.202587ms)
|
||||||
|
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (125.001422ms)
|
||||||
|
✔ async transactions refuse before invoking their function (94.629513ms)
|
||||||
|
✔ recordTask keeps sync reads and a role write apart (250.930381ms)
|
||||||
|
✔ read_at must be one canonical UTC format, so the projection compares strings safely (152.088447ms)
|
||||||
|
✔ a bad entry refuses the whole record (161.177091ms)
|
||||||
|
✔ taskView reads the projection for one business (203.275656ms)
|
||||||
|
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (295.117707ms)
|
||||||
|
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (453.779089ms)
|
||||||
|
✔ without an adapter the server refuses every task verb (305.000273ms)
|
||||||
|
✔ the runtime refuses an invalid adapter and closes a valid one (278.560511ms)
|
||||||
|
✔ the process loads the S3 adapter from plain-data trackers (340.686837ms)
|
||||||
|
✔ socket capability stamps launch identity; shared views use wire, no SQL client (233.452782ms)
|
||||||
|
✔ two wire claims serialize; a lost reply never automatically retries (265.170068ms)
|
||||||
|
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (152.168362ms)
|
||||||
|
✔ client preserves UTF-8 when a response divides a multibyte character (12.521387ms)
|
||||||
|
✔ committed mutation followed by dropped reply reports unknown and is never retried (229.685316ms)
|
||||||
|
ℹ tests 67
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 67
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 3438.917252
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
✔ config directory and file path follow MOSAIC_CONFIG (2.284009ms)
|
||||||
|
✔ the fixture business validates and comes back frozen (6.487939ms)
|
||||||
|
✔ two instances may share a definition (2.275159ms)
|
||||||
|
✔ top-level refusals (6.073799ms)
|
||||||
|
✔ arbiters and projects (9.991129ms)
|
||||||
|
✔ role instances (3.798752ms)
|
||||||
|
✔ Vikunja bots (8.303249ms)
|
||||||
|
✔ a role without Vikunja takes no tracker block (5.495114ms)
|
||||||
|
✔ credential references match the definition's services (3.63747ms)
|
||||||
|
✔ launch (8.543695ms)
|
||||||
|
✔ loadBusiness: file checks (2.248537ms)
|
||||||
|
✔ loadBusiness: not a regular file (55.746268ms)
|
||||||
|
✔ loading writes nothing (1.12094ms)
|
||||||
|
✔ names that are Object.prototype properties don't count as declared (3.05957ms)
|
||||||
|
✔ the shipped example refuses as written and validates once filled in (0.590772ms)
|
||||||
|
✔ usage errors exit 4 (363.427862ms)
|
||||||
|
✔ validate: a good business exits 0 and prints instance digests (85.361898ms)
|
||||||
|
✔ validate: project files (436.931166ms)
|
||||||
|
✔ validate: missing files and a broken system config (321.481183ms)
|
||||||
|
✔ validate: credential reference problems exit 2 and name each one (78.04229ms)
|
||||||
|
✔ validate: a token file inside the repository is refused (75.149693ms)
|
||||||
|
✔ validate: role definitions come from MOSAIC_ROLES_DIR (223.179074ms)
|
||||||
|
✔ resolve: prints one instance's record (252.287269ms)
|
||||||
|
✔ resolve: refusals (482.205406ms)
|
||||||
|
✔ parse: exactly one of file or env, plus the service's date (3.241377ms)
|
||||||
|
✔ check: a good file has no problems (1.062324ms)
|
||||||
|
✔ check never opens the file: a write-only token passes (0.520883ms)
|
||||||
|
✔ check: file problems (1.250663ms)
|
||||||
|
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (1.065013ms)
|
||||||
|
✔ check: dates and environment references (0.612212ms)
|
||||||
|
✔ path and load (3.24501ms)
|
||||||
|
✔ refusals (1.971353ms)
|
||||||
|
✔ systemVars flattens the validated config (2.380655ms)
|
||||||
|
✔ precedence: system, business, project, project role, agent (6.614577ms)
|
||||||
|
✔ limits narrow the definition and never widen it (3.222887ms)
|
||||||
|
✔ role.launch stays within-role only for the instance the launch block names (6.684685ms)
|
||||||
|
✔ limits.authority without role.launch leaves the launcher with no launch block (2.69868ms)
|
||||||
|
✔ limits.authority narrows cross-role actions too (1.634008ms)
|
||||||
|
✔ classify (1.721356ms)
|
||||||
|
✔ the record carries what the broker and launcher need (1.541191ms)
|
||||||
|
✔ digest: key order doesn't matter, any value change does (14.70505ms)
|
||||||
|
✔ refusals (3.474474ms)
|
||||||
|
✔ the four shipped version 2 roles load (4.757051ms)
|
||||||
|
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.42544ms)
|
||||||
|
✔ shipped authority follows the note's table (0.578519ms)
|
||||||
|
✔ version 1 files keep loading with no authority (1.213931ms)
|
||||||
|
✔ the conductor policy isn't a role (0.277292ms)
|
||||||
|
✔ a missing role file is exit 4, a symbolic link too (0.489879ms)
|
||||||
|
✔ version 2 refusals (1.712467ms)
|
||||||
|
✔ authority: closed vocabulary, no gated-only action, no overlap (2.904921ms)
|
||||||
|
✔ credentials: Gitea scopes (0.959458ms)
|
||||||
|
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.232639ms)
|
||||||
|
✔ credentials: services (0.60855ms)
|
||||||
|
✔ contract: a non-empty regular Markdown file beside the role file (0.673426ms)
|
||||||
|
✔ every key names known layers and a merge rule (1.019171ms)
|
||||||
|
✔ unknown keys and wrong layers refuse (0.955361ms)
|
||||||
|
✔ types (2.561672ms)
|
||||||
|
✔ merge: defaults, then the most specific layer wins (0.358745ms)
|
||||||
|
✔ merge: limits only narrow, and provenance lists each source (0.490927ms)
|
||||||
|
✔ merge doesn't change its inputs (0.201055ms)
|
||||||
|
ℹ tests 60
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 60
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2404.316662
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (2.963073ms)
|
||||||
|
✔ approvals: the ledger is appended and folded into open requests with bind and approval states (3.178894ms)
|
||||||
|
✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.720524ms)
|
||||||
|
✔ approvals: a button approves only on its own request message with the matching custom id (0.530479ms)
|
||||||
|
✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (20.512863ms)
|
||||||
|
✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (13.406501ms)
|
||||||
|
✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (8.308285ms)
|
||||||
|
✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (5.005ms)
|
||||||
|
✔ authorize: open channel, listed user (2.462329ms)
|
||||||
|
✔ authorize: wrong guild (0.211188ms)
|
||||||
|
✔ authorize: no guild (DM) (0.382637ms)
|
||||||
|
✔ authorize: unlisted channel (0.243141ms)
|
||||||
|
✔ authorize: unknown channel, no info (0.283841ms)
|
||||||
|
✔ authorize: thread of listed parent (0.194418ms)
|
||||||
|
✔ authorize: thread of unlisted parent (0.241093ms)
|
||||||
|
✔ authorize: text channel that is not a thread and not listed (0.187886ms)
|
||||||
|
✔ authorize: unlisted user (1.473786ms)
|
||||||
|
✔ authorize: no author (0.518056ms)
|
||||||
|
✔ authorize: bot author (listed id, bot flag) (0.245026ms)
|
||||||
|
✔ authorize: system author (0.327851ms)
|
||||||
|
✔ authorize: the bot itself (0.104269ms)
|
||||||
|
✔ authorize: webhook (0.104934ms)
|
||||||
|
✔ authorize: mention channel without mention (0.751354ms)
|
||||||
|
✔ authorize: mention channel with bot mention (0.14485ms)
|
||||||
|
✔ authorize: mention channel with @everyone only (0.257862ms)
|
||||||
|
✔ authorize: mention channel mentioning someone else (0.100179ms)
|
||||||
|
✔ authorize: mention channel, content says @bot but mentions empty (0.149361ms)
|
||||||
|
✔ authorize: private thread under mention channel, mentioned (0.125426ms)
|
||||||
|
✔ authorize: private thread under mention channel, not mentioned (0.082438ms)
|
||||||
|
✔ authorize: thread in another guild per channel info (0.08409ms)
|
||||||
|
✔ authorize: not an object (0.073476ms)
|
||||||
|
✔ authorize: no id (0.071977ms)
|
||||||
|
✔ authorize: oversize content is accepted and flagged (0.081707ms)
|
||||||
|
✔ authorize: exactly the limit is not oversize (0.075284ms)
|
||||||
|
✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.513239ms)
|
||||||
|
✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.152837ms)
|
||||||
|
✔ binding: a complete binding validates and is frozen (3.017009ms)
|
||||||
|
✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.851649ms)
|
||||||
|
✔ binding: empty allowlists refuse (0.544457ms)
|
||||||
|
✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.390612ms)
|
||||||
|
✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.978558ms)
|
||||||
|
✔ binding: file must be 0600, regular, not a symlink (1.682533ms)
|
||||||
|
✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (5.080671ms)
|
||||||
|
✔ cli: check refuses a non-0600 token file with exit 2 before any network use (142.347456ms)
|
||||||
|
✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (1.91407ms)
|
||||||
|
✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (442.993851ms)
|
||||||
|
✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (264.506026ms)
|
||||||
|
✔ cli: run refuses when STOP is present, before any network use (154.948451ms)
|
||||||
|
✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.82821ms)
|
||||||
|
✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.259171ms)
|
||||||
|
✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (21.381395ms)
|
||||||
|
✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (25.038607ms)
|
||||||
|
✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (2.602428ms)
|
||||||
|
✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.333856ms)
|
||||||
|
✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.425041ms)
|
||||||
|
✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.21173ms)
|
||||||
|
✔ turn: a second message during a turn is held by the engine, both get their own reply and record (35.202423ms)
|
||||||
|
✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (2.823213ms)
|
||||||
|
✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.616033ms)
|
||||||
|
✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.054458ms)
|
||||||
|
✔ restart: an inbox with three ids and a replay of the same three produces zero turns (44.52569ms)
|
||||||
|
✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.442936ms)
|
||||||
|
✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.794603ms)
|
||||||
|
✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (3.651826ms)
|
||||||
|
✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (1.389594ms)
|
||||||
|
✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.749633ms)
|
||||||
|
✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.956586ms)
|
||||||
|
✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.736482ms)
|
||||||
|
✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (1.868026ms)
|
||||||
|
✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.550336ms)
|
||||||
|
✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (6.067062ms)
|
||||||
|
✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (1.645746ms)
|
||||||
|
✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.599939ms)
|
||||||
|
✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.741713ms)
|
||||||
|
✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.886904ms)
|
||||||
|
✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.094096ms)
|
||||||
|
✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.463935ms)
|
||||||
|
✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (3.263437ms)
|
||||||
|
✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.683573ms)
|
||||||
|
✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (1.682811ms)
|
||||||
|
✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (3.566397ms)
|
||||||
|
✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.460912ms)
|
||||||
|
✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (78.754891ms)
|
||||||
|
✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (49.805976ms)
|
||||||
|
✔ engine: one prompt, one turn, text and usage come back (40.137415ms)
|
||||||
|
✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (354.984992ms)
|
||||||
|
✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (248.500124ms)
|
||||||
|
✔ engine: timeout sends abort and fails only that turn; the process stays (106.191287ms)
|
||||||
|
✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (239.999019ms)
|
||||||
|
✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (150.938225ms)
|
||||||
|
✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (212.826758ms)
|
||||||
|
✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.581606ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.54869ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.639715ms)
|
||||||
|
✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (431.214694ms)
|
||||||
|
✔ engine: a malformed JSONL line fails the turn, not the process (43.63606ms)
|
||||||
|
✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (49.40699ms)
|
||||||
|
✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.690703ms)
|
||||||
|
✔ gateway: missed ack closes the socket and resumes with the last sequence (1.907767ms)
|
||||||
|
✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.53113ms)
|
||||||
|
✔ gateway: op 9 resumable resumes (0.526163ms)
|
||||||
|
✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (1.061126ms)
|
||||||
|
✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.548698ms)
|
||||||
|
✔ gateway: close() is final and unparseable frames are ignored (0.552598ms)
|
||||||
|
✔ git: config validation is strict, needs write: true, a work tree and a private token file (102.597597ms)
|
||||||
|
✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (51.583927ms)
|
||||||
|
✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (113.962396ms)
|
||||||
|
✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.509335ms)
|
||||||
|
✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (119.775668ms)
|
||||||
|
✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (122.912009ms)
|
||||||
|
✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (147.08931ms)
|
||||||
|
✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (263.840565ms)
|
||||||
|
✔ git: push pushes the named branch only and reports up to date (98.299482ms)
|
||||||
|
✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (111.593045ms)
|
||||||
|
✔ git: the credential helper answers get over https from a private file and nothing else (294.872892ms)
|
||||||
|
✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (960.529764ms)
|
||||||
|
✔ lock: the claim is exclusive; a second start against a live owner refuses (5.227477ms)
|
||||||
|
✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (6.035809ms)
|
||||||
|
✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.431433ms)
|
||||||
|
✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (3.005199ms)
|
||||||
|
✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (82.870452ms)
|
||||||
|
✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (414.27476ms)
|
||||||
|
✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.458791ms)
|
||||||
|
✔ lock: a process whose start marker or boot id cannot be read refuses to claim (0.406162ms)
|
||||||
|
✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.846589ms)
|
||||||
|
✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (53.748375ms)
|
||||||
|
✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (152.58686ms)
|
||||||
|
✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (79.712479ms)
|
||||||
|
✔ notices: a kind is recorded per UTC day and found again (0.482002ms)
|
||||||
|
✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (74.28234ms)
|
||||||
|
✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (51.740288ms)
|
||||||
|
✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (46.609843ms)
|
||||||
|
✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (114.131415ms)
|
||||||
|
✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (843.858294ms)
|
||||||
|
✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (3.413377ms)
|
||||||
|
✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.131492ms)
|
||||||
|
✔ rest: content and nonce limits are enforced locally; typing never throws (0.739157ms)
|
||||||
|
✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.930648ms)
|
||||||
|
✔ setspark config: a bare https or loopback origin, a private key file, a principal (7.088359ms)
|
||||||
|
✔ setspark config: reaches the tools config and the binding as a fixed key (3.708867ms)
|
||||||
|
✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.812564ms)
|
||||||
|
✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.454088ms)
|
||||||
|
✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (41.083503ms)
|
||||||
|
✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (12.675817ms)
|
||||||
|
✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (12.576069ms)
|
||||||
|
✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.450085ms)
|
||||||
|
✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.721963ms)
|
||||||
|
✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.10125ms)
|
||||||
|
✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1012.360182ms)
|
||||||
|
✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.536678ms)
|
||||||
|
✔ setspark verbs: writes carry the turn's key and the asserted requester, reads carry no key, and the api key never appears in text or details (9.111159ms)
|
||||||
|
✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.584368ms)
|
||||||
|
✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.24147ms)
|
||||||
|
✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.660486ms)
|
||||||
|
✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.489669ms)
|
||||||
|
✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (5.04174ms)
|
||||||
|
✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.854329ms)
|
||||||
|
✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.020608ms)
|
||||||
|
✔ tools: listing and search caps hold (12.583785ms)
|
||||||
|
✔ tools: credential shapes are caught; ordinary prose and ids are not (1.318736ms)
|
||||||
|
✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (12.793439ms)
|
||||||
|
✔ tools: an unreadable file under the root is skipped by search and refused by read (2.300647ms)
|
||||||
|
✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.501369ms)
|
||||||
|
✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.402268ms)
|
||||||
|
✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (4.687671ms)
|
||||||
|
✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.815983ms)
|
||||||
|
✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.970733ms)
|
||||||
|
✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.551934ms)
|
||||||
|
✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1033.99121ms)
|
||||||
|
✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (5.458972ms)
|
||||||
|
✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.328179ms)
|
||||||
|
✔ web: web_search asks the instance for json, returns at most ten clean results, and refuses a bad query, a down instance or an unusable answer (3.468037ms)
|
||||||
|
✔ web: the tool set enables the web tools only with a web key, counts them in the budget, and records url, status and hits (2.925825ms)
|
||||||
|
ℹ tests 173
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 173
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2805.660296
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
✔ the boot config is checked before anything starts (516.214198ms)
|
||||||
|
✔ a business with no tracker entry refuses task verbs (202.040347ms)
|
||||||
|
✔ credential.expiring and .expired are recorded once per instance (277.2536ms)
|
||||||
|
✔ a token file that changes on disk records credential.changed (129.625135ms)
|
||||||
|
✔ autostart polls, reconciles and retries a startup the tracker was down for (150.836051ms)
|
||||||
|
✔ a refusal a restart must clear is not retried by the poll (100.523777ms)
|
||||||
|
✔ a poll that fires while two are queued is dropped (116.03428ms)
|
||||||
|
✔ close waits for a running verb and refuses one that has not started (286.520729ms)
|
||||||
|
✔ the bundled Vikunja is the pinned upstream image the runbook names (0.982267ms)
|
||||||
|
✔ every published port is on 127.0.0.1, and no secret is in the file (0.354809ms)
|
||||||
|
✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (872.446139ms)
|
||||||
|
✔ the recorded task bodies pass the checks S3 applies to every read (0.588013ms)
|
||||||
|
✔ the client works against the fake over real HTTP with the platform fetch (276.500664ms)
|
||||||
|
✔ a correct install starts, and the first reconcile records tasks that already exist (558.033859ms)
|
||||||
|
✔ verbs refuse while a business is starting and after startup refused it (194.991321ms)
|
||||||
|
✔ startup refuses a token that can do more than its role needs (425.35624ms)
|
||||||
|
✔ startup refuses an unsupported version and flags an untested one (358.413565ms)
|
||||||
|
✔ startup refuses a board that the runbook did not install (523.518323ms)
|
||||||
|
✔ startup refuses a project the sync bot cannot read (102.381308ms)
|
||||||
|
✔ startup refuses a configured label the pm bot cannot see (164.637086ms)
|
||||||
|
✔ startup refuses an expired credential and a missing sync credential (324.959769ms)
|
||||||
|
✔ an unreachable tracker refuses with tracker-unavailable (257.875281ms)
|
||||||
|
✔ an edit in the UI is recorded once, with the fields that changed (676.606055ms)
|
||||||
|
✔ a move between open buckets is seen on the board, though updated does not change (231.489796ms)
|
||||||
|
✔ a person's comment is counted and a bot's is not (290.191335ms)
|
||||||
|
✔ the hourly reconcile catches a comment through comment_count (253.210118ms)
|
||||||
|
✔ a task closed in the UI leaves the open view with its done bucket (543.063112ms)
|
||||||
|
✔ a task that leaves the board is recorded as deleted, moved or out of reach (379.240669ms)
|
||||||
|
✔ a poll that read before a verb wrote does not overwrite the verb (289.658394ms)
|
||||||
|
✔ a tracker fault during a tick is reported and the next tick catches up (463.374074ms)
|
||||||
|
✔ a malformed answer refuses the tick with tracker-shape (203.05616ms)
|
||||||
|
✔ no token value reaches the database, the log or a refusal (392.288491ms)
|
||||||
|
✔ the first look at a task counts only comments inside the window (204.484884ms)
|
||||||
|
✔ task.create needs a recorded human request and a requirement id (685.311567ms)
|
||||||
|
✔ only labels named in the business file can be written (295.994765ms)
|
||||||
|
✔ task.schedule sets and clears a due date and relations (307.961545ms)
|
||||||
|
✔ assign and reassign move the role bots and record task.assigned (395.036157ms)
|
||||||
|
✔ task.update.assigned is for the assignee and records task.state (428.5151ms)
|
||||||
|
✔ a wrong expected digest records task.conflict and writes nothing (341.556182ms)
|
||||||
|
✔ a cross-role verb needs a resolved decision, used once (495.921449ms)
|
||||||
|
✔ task.close needs a verdict; after it every verb refuses with task-done (429.584203ms)
|
||||||
|
✔ a lost answer is settled by a re-read and never retried (382.674631ms)
|
||||||
|
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (195.778392ms)
|
||||||
|
✔ a task the sync bot cannot read refuses and records nothing (104.964243ms)
|
||||||
|
✔ verbs and polls for one business run one at a time (188.87669ms)
|
||||||
|
✔ a due date with milliseconds is written to the second (181.682299ms)
|
||||||
|
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (126.029473ms)
|
||||||
|
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (97.017427ms)
|
||||||
|
✔ a create whose final read fails succeeds and records task.created (108.52731ms)
|
||||||
|
✔ an edit between the last write and the final read shows as external on the next poll (141.955479ms)
|
||||||
|
✔ task.created is recorded when a later label write fails (95.585552ms)
|
||||||
|
ℹ tests 51
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 51
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 5109.136747
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
OK status with missing harness credential exits 3 and still lists accounts
|
||||||
|
OK status reports harness credential (read-only) + mosaic accounts
|
||||||
|
OK api key material never reaches output
|
||||||
|
OK oauth token material never reaches output
|
||||||
|
OK unparseable credential file exits 2
|
||||||
|
OK symlinked credential file exits 4
|
||||||
|
OK env-side credential names reported
|
||||||
|
OK env var values never reach output
|
||||||
|
OK accounts without an accounts dir reports none and creates nothing
|
||||||
|
OK accounts lists files and marks the active one
|
||||||
|
OK loose account perms flagged in listing
|
||||||
|
OK agent --auth with missing account file refuses (exit 4)
|
||||||
|
OK agent --auth with non-0600 account file refuses
|
||||||
|
OK agent --auth with invalid account name refuses
|
||||||
|
OK auth.sh without valid config refuses
|
||||||
|
|
||||||
|
selftest: 15 passed, 0 failed
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
Note: switching to 'c9ef7ee49f0785287de7ff41130dd4761ad2c7e3'.
|
||||||
|
|
||||||
|
You are in 'detached HEAD' state. You can look around, make experimental
|
||||||
|
changes and commit them, and you can discard any commits you make in this
|
||||||
|
state without impacting any branches by switching back to a branch.
|
||||||
|
|
||||||
|
If you want to create a new branch to retain commits you create, you may
|
||||||
|
do so (now or later) by using -c with the switch command. Example:
|
||||||
|
|
||||||
|
git switch -c <new-branch-name>
|
||||||
|
|
||||||
|
Or undo this operation with:
|
||||||
|
|
||||||
|
git switch -
|
||||||
|
|
||||||
|
Turn off this advice by setting config variable advice.detachedHead to false
|
||||||
|
|
||||||
|
Not currently on any branch.
|
||||||
|
nothing to commit, working tree clean
|
||||||
|
Note: switching to 'c9ef7ee49f0785287de7ff41130dd4761ad2c7e3'.
|
||||||
|
|
||||||
|
You are in 'detached HEAD' state. You can look around, make experimental
|
||||||
|
changes and commit them, and you can discard any commits you make in this
|
||||||
|
state without impacting any branches by switching back to a branch.
|
||||||
|
|
||||||
|
If you want to create a new branch to retain commits you create, you may
|
||||||
|
do so (now or later) by using -c with the switch command. Example:
|
||||||
|
|
||||||
|
git switch -c <new-branch-name>
|
||||||
|
|
||||||
|
Or undo this operation with:
|
||||||
|
|
||||||
|
git switch -
|
||||||
|
|
||||||
|
Turn off this advice by setting config variable advice.detachedHead to false
|
||||||
|
|
||||||
|
OK dry-run: allowed change, exit 0, nothing committed (exit 0)
|
||||||
|
OK dry-run committed nothing
|
||||||
|
OK apply: allowed change exits 0 (exit 0)
|
||||||
|
OK apply: attribution in commit subject
|
||||||
|
OK apply: target tree clean after commit
|
||||||
|
OK disallowed path refused (exit 1)
|
||||||
|
OK disallowed path: target untouched
|
||||||
|
OK syntax gate refused broken .mjs (exit 1)
|
||||||
|
OK syntax gate: target untouched
|
||||||
|
OK suite failure refused (exit 1)
|
||||||
|
OK suite failure: target reverted to clean
|
||||||
|
OK disabled policy refused (exit 2)
|
||||||
|
OK disabled policy: target untouched
|
||||||
|
OK failed run refused (exit 1)
|
||||||
|
OK failed run: target untouched
|
||||||
|
OK missing run exits 4 (exit 4)
|
||||||
|
OK invalid policy exits 2 (exit 2)
|
||||||
|
|
||||||
|
selftest: 17 passed, 0 failed
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
OK absent adapter defaults to pi
|
||||||
|
OK adapter mock validates (exit 0)
|
||||||
|
OK unsupported adapter exits 2 (exit 2)
|
||||||
|
OK env exports adapter
|
||||||
|
OK bootstrap creates default when absent (exit 0)
|
||||||
|
OK bootstrap wrote config file
|
||||||
|
OK bootstrap is idempotent on existing config (exit 0)
|
||||||
|
OK bootstrap did not rewrite existing config
|
||||||
|
OK validate missing config exits 3 (exit 3)
|
||||||
|
OK malformed JSON exits 2 (exit 2)
|
||||||
|
OK unsupported configVersion exits 2 (exit 2)
|
||||||
|
OK unknown top-level key exits 2 (exit 2)
|
||||||
|
OK unknown execution key exits 2 (exit 2)
|
||||||
|
OK unsupported backend exits 2 (exit 2)
|
||||||
|
OK unsupported environment exits 2 (exit 2)
|
||||||
|
OK relative dataRoot exits 2 (exit 2)
|
||||||
|
OK non-canonical dataRoot exits 2 (exit 2)
|
||||||
|
OK filesystem root dataRoot exits 2 (exit 2)
|
||||||
|
OK home directory dataRoot exits 2 (exit 2)
|
||||||
|
OK dataRoot containing config dir exits 2 (exit 2)
|
||||||
|
OK control character in provider exits 2 (exit 2)
|
||||||
|
OK symlinked config file exits 2 (exit 2)
|
||||||
|
OK env exports resolve correctly
|
||||||
|
OK failed validation modified nothing
|
||||||
|
|
||||||
|
selftest: 24 passed, 0 failed
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
toolchain: node v26.8.1
|
||||||
|
|
||||||
|
OK syntax: packages/discord/src/approvals.mjs
|
||||||
|
OK syntax: packages/discord/src/authorize.mjs
|
||||||
|
OK syntax: packages/discord/src/binding.mjs
|
||||||
|
OK syntax: packages/discord/src/cli.mjs
|
||||||
|
OK syntax: packages/discord/src/connector.mjs
|
||||||
|
OK syntax: packages/discord/src/context.mjs
|
||||||
|
OK syntax: packages/discord/src/engine-pi.mjs
|
||||||
|
OK syntax: packages/discord/src/errors.mjs
|
||||||
|
OK syntax: packages/discord/src/gateway.mjs
|
||||||
|
OK syntax: packages/discord/src/git.mjs
|
||||||
|
OK syntax: packages/discord/src/journal.mjs
|
||||||
|
OK syntax: packages/discord/src/rest.mjs
|
||||||
|
OK syntax: packages/discord/src/setspark.mjs
|
||||||
|
OK syntax: packages/discord/src/tools.mjs
|
||||||
|
OK syntax: packages/discord/src/web.mjs
|
||||||
|
OK syntax: packages/discord/bin/git-credential.mjs
|
||||||
|
OK syntax: packages/discord/extension/tools.mjs
|
||||||
|
OK syntax: packages/discord/tests/approvals.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/authorize.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/binding.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/connector.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/context.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/engine.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/fake-pi.mjs
|
||||||
|
OK syntax: packages/discord/tests/gateway.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/git.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/helpers.mjs
|
||||||
|
OK syntax: packages/discord/tests/journal.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/recover.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/rest.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/setspark.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/tools.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/web.test.mjs
|
||||||
|
OK syntax: packages/discord/fixtures/claim-worker.mjs
|
||||||
|
OK syntax: packages/discord/fixtures/legacy-owner-worker.mjs
|
||||||
|
OK syntax: scripts/discord.sh
|
||||||
|
OK syntax: scripts/discord-service.sh
|
||||||
|
OK packages/discord declares no dependencies
|
||||||
|
OK no bot-token-shaped string in packages/discord
|
||||||
|
OK fixture binding uses placeholder ids only
|
||||||
|
OK fixture binding validates
|
||||||
|
OK real pi with the extension exposes exactly list_dir, read_file, search and no built-in tool
|
||||||
|
OK real pi with a writable root exposes exactly the three reads plus write_file and edit_file, and writes nothing at start
|
||||||
|
OK real pi with a web key exposes the three reads plus web_fetch and web_search, and no write tool without a writable root
|
||||||
|
OK real pi with a git root exposes the reads, writes and the four git verbs, commits nothing at start, and never shows the token
|
||||||
|
OK real pi with protocol vault adds reserve_id to the git verbs
|
||||||
|
OK real pi with a setspark key exposes the reads and the eight record verbs, no counters, and never shows the key
|
||||||
|
OK real pi refuses a git key on a read-only root (fail closed)
|
||||||
|
OK real pi with the pilot flags (--no-tools) exposes no tool at all
|
||||||
|
OK real pi exits non-zero without MOSAIC_DISCORD_TOOLS: no session, no tools (fail closed)
|
||||||
|
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
|
||||||
|
OK node --test packages/discord/tests/ (ℹ pass 173)
|
||||||
|
OK scripts/discord.sh --help exits 0
|
||||||
|
OK scripts/discord.sh check without a binding exits 4
|
||||||
|
OK scripts/discord.sh recover without a binding exits 4
|
||||||
|
OK scripts/discord.sh reload without a binding exits 4
|
||||||
|
OK scripts/discord-service.sh without a command exits 4
|
||||||
|
OK service unit renders with the repository path, a supervised run as the main process, exit 3 never retried, and reload as SIGHUP
|
||||||
|
OK service install writes the rendered unit (0644) and leaves no temp file
|
||||||
|
OK service install a second time reports unchanged
|
||||||
|
OK systemd-analyze verify accepts the rendered unit
|
||||||
|
OK service uninstall removes the unit file
|
||||||
|
OK service install with an unknown flag exits 4
|
||||||
|
OK service install with USER unset finishes and names the account for lingering
|
||||||
|
|
||||||
|
discord suite: 64 passed, 0 failed
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
OK initial ordinary-file install
|
||||||
|
OK installed tree matches canonical source
|
||||||
|
OK installed tree has no symlinks
|
||||||
|
OK check detects installation drift
|
||||||
|
OK sync refuses to overwrite installation drift
|
||||||
|
OK check detects an extra destination file
|
||||||
|
OK check detects an extra destination directory
|
||||||
|
OK check rejects a destination symlink
|
||||||
|
OK sync accepts a canonical source update
|
||||||
|
OK updated installation matches canonical source
|
||||||
|
scripts/test-extension-package.sh: line 14: 3836966 Killed "$@" > /dev/null 2>&1
|
||||||
|
OK forced interruption kills the replacing process
|
||||||
|
OK next invocation recovers old consistent installation
|
||||||
|
OK interrupted replacement rolled back
|
||||||
|
OK sync succeeds after interruption recovery
|
||||||
|
OK unlocked stale lock file does not block
|
||||||
|
OK active lock refuses a concurrent sync
|
||||||
|
OK source symlink fails closed
|
||||||
|
OK nested second entrypoint fails closed
|
||||||
|
|
||||||
|
extension package selftest: 18 passed, 0 failed
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
toolchain: node v26.8.1, python 3.12.8, jsonschema 4.26.0
|
||||||
|
|
||||||
|
OK syntax: scripts/foundation-inspect.mjs
|
||||||
|
OK syntax: scripts/foundation/strict-json.mjs
|
||||||
|
OK syntax: scripts/foundation/canonical.mjs
|
||||||
|
OK syntax: scripts/foundation/resolve.mjs
|
||||||
|
OK syntax: scripts/foundation/validate-record.mjs
|
||||||
|
OK syntax: scripts/foundation/fixtures/build-fixtures.mjs
|
||||||
|
OK syntax: scripts/foundation/canonical.test.mjs
|
||||||
|
OK syntax: scripts/foundation/cli.test.mjs
|
||||||
|
OK syntax: scripts/foundation/fixtures.test.mjs
|
||||||
|
OK syntax: scripts/foundation/resolve.test.mjs
|
||||||
|
OK syntax: scripts/foundation/strict-json.test.mjs
|
||||||
|
OK syntax: scripts/foundation/verify-schema.py (ast only; no bytecode written)
|
||||||
|
OK fixture generator runs
|
||||||
|
OK checked-in fixtures/bundles equal a fresh generation
|
||||||
|
OK checked-in fixtures/raw equal a fresh generation
|
||||||
|
OK checked-in fixtures/index.json equal a fresh generation
|
||||||
|
OK checked-in demo bundles equal a fresh generation
|
||||||
|
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
|
||||||
|
OK node --test scripts/foundation/ (ℹ pass 80)
|
||||||
|
OK differential schema oracle: PASS: differential schema oracle (finite corpus; compatibility evidence, not equivalence proof)
|
||||||
|
platform witness: strftime('%Y') for year 999 -> '999' (pinned checker refuses years 0001..0999)
|
||||||
|
node v26.8.1; corpus 1568 records (38 pinned fixtures, 478 unique bundle records, 1052 typeCase/mutation/lexical cases)
|
||||||
|
schema column: agree-valid 540, agree-invalid 991, DISAGREEMENTS 0; strict-only (parser-bound) cases: 27; unsupported-kind records not schema-assessed by the inspector: 10
|
||||||
|
profile column (schema-valid records only): profile-valid 510, profile-invalid 30
|
||||||
|
profile refusals asserted: 30 schema-agreed-valid records refused only by the strict typed-string profile (rule profile-pattern-mismatch), 12 declared by name; 73 named probes verified against declared schema/profile columns
|
||||||
|
OK oracle: zero schema-column disagreements with the pinned checker
|
||||||
|
OK oracle: strict-only profile refusals are counted and asserted
|
||||||
|
OK demo: permitted read preview exits 0 (exit 0)
|
||||||
|
OK demo: permitted file.change preview exits 0 (exit 0)
|
||||||
|
OK demo: assignment.change proposal is unresolved (exit 3) (exit 3)
|
||||||
|
OK demo: revoked registration is refused (exit 3) (exit 3)
|
||||||
|
OK demo: message is not authority (exit 3) (exit 3)
|
||||||
|
OK usage: no arguments exits 2 (exit 2)
|
||||||
|
OK io: missing file exits 4 (exit 4)
|
||||||
|
OK io: directory exits 4 (exit 4)
|
||||||
|
OK io: symlink exits 4 (O_NOFOLLOW) (exit 4)
|
||||||
|
OK bound: oversize fixture exits 2 (exit 2)
|
||||||
|
OK profile: one final LF in a typed selection id is refused before admission (exit 2) (exit 2)
|
||||||
|
OK profile: two final LFs fail the schema pattern itself (exit 2) (exit 2)
|
||||||
|
OK profile: escaped newlines in free-form text stay allowed (exit 0) (exit 0)
|
||||||
|
OK profile refusal is invalid-request/profile-pattern-mismatch with selection and operation withheld, value not echoed
|
||||||
|
OK text output starts with the disclaimer
|
||||||
|
OK json output is valid JSON with result allowed and exactly the charter §7 fields
|
||||||
|
OK json golden matches byte-for-byte
|
||||||
|
OK sandboxed bundle run (env -i, PATH=/nonexistent) produced the unresolved proposal
|
||||||
|
OK sandbox inventory (path/type/size/mode/uid/gid/inode/mtime/sha256) unchanged by runs
|
||||||
|
OK canary never printed (bundle run and credential-file run)
|
||||||
|
OK a non-bundle JSON file is refused at the shape gate, not read into output
|
||||||
|
OK no field of the non-bundle file is echoed
|
||||||
|
|
||||||
|
selftest: 44 passed, 0 failed
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
toolchain: node v26.8.1, git version 2.55.0
|
||||||
|
|
||||||
|
OK syntax: packages/queue/src/cli.mjs
|
||||||
|
OK syntax: packages/queue/src/errors.mjs
|
||||||
|
OK syntax: packages/queue/src/io.mjs
|
||||||
|
OK syntax: packages/queue/src/lock.mjs
|
||||||
|
OK syntax: packages/queue/src/queue.mjs
|
||||||
|
OK syntax: packages/queue/src/review.mjs
|
||||||
|
OK syntax: packages/queue/src/store.mjs
|
||||||
|
OK syntax: packages/queue/tests/commit.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/data.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/dispatch.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/helpers.mjs
|
||||||
|
OK syntax: packages/queue/tests/lock.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/migration.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/review.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/store.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/write.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/fixtures/fake-gitea.mjs
|
||||||
|
OK syntax: packages/queue/tests/fixtures/kill-at.mjs
|
||||||
|
OK syntax: packages/queue/tests/fixtures/lock-child.mjs
|
||||||
|
OK syntax: packages/queue/tests/fixtures/mosaic-pre-a2.sh
|
||||||
|
OK syntax: scripts/queue-commit.sh
|
||||||
|
OK syntax: scripts/git-hooks/pre-commit
|
||||||
|
OK syntax: scripts/mosaic
|
||||||
|
OK queue-commit.sh, the guard and scripts/mosaic are executable
|
||||||
|
OK packages/queue declares no dependencies
|
||||||
|
ℹ tests 148
|
||||||
|
ℹ pass 148
|
||||||
|
ℹ fail 0
|
||||||
|
OK node --test packages/queue/tests/
|
||||||
|
OK scripts/mosaic queue help
|
||||||
|
skip queue verify and render --check: this checkout (/home/jwoltje/filbert-scratch/r38b/cand) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
|
||||||
|
|
||||||
|
queue suite: 27 passed, 0 failed
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
OK valid RELEASE resolves (exit 0)
|
||||||
|
OK invalid RELEASE exits 1 (exit 1)
|
||||||
|
OK missing RELEASE exits 1 (exit 1)
|
||||||
|
OK valid RELEASE leaves image tag consistent with version
|
||||||
|
skip state-machine cases (docker daemon unavailable)
|
||||||
|
|
||||||
|
selftest: 4 passed, 0 failed
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
OK valid task validates (exit 0)
|
||||||
|
OK unknown task key exits 2 (exit 2)
|
||||||
|
OK unsupported taskVersion exits 2 (exit 2)
|
||||||
|
OK invalid task id exits 2 (exit 2)
|
||||||
|
OK empty prompt exits 2 (exit 2)
|
||||||
|
OK NUL in expectExact exits 2 (exit 2)
|
||||||
|
OK out-of-range timeout exits 2 (exit 2)
|
||||||
|
OK missing mission file exits 4 (exit 4)
|
||||||
|
OK task with valid mission validates (exit 0)
|
||||||
|
OK invalid mission exits 2 (exit 2)
|
||||||
|
OK validate missing task exits 4 (exit 4)
|
||||||
|
OK validation does not modify the task file
|
||||||
|
OK prune dry-run exits 0 (exit 0)
|
||||||
|
OK dry-run deleted nothing
|
||||||
|
OK prune --keep=2 --yes removes oldest (exit 0)
|
||||||
|
OK kept exactly 2 newest runs
|
||||||
|
OK newest run kept, oldest pruned
|
||||||
|
OK append-only receipt written (3 entries)
|
||||||
|
OK sessions/workspaces untouched by prune
|
||||||
|
OK prune with invalid keep exits 4 (exit 4)
|
||||||
|
skip adapter seam cases (docker daemon unavailable)
|
||||||
|
skip workspace/capability cases (docker daemon unavailable)
|
||||||
|
skip live task cases (docker unavailable)
|
||||||
|
OK onboard without name exits 4 (non-interactive) (exit 4)
|
||||||
|
OK onboard --name renders profile (exit 0)
|
||||||
|
OK profile written
|
||||||
|
OK canon structure: required filled, optional placeholdered
|
||||||
|
OK canon sections present
|
||||||
|
FAIL user recall run succeeds (exit 1)
|
||||||
|
FAIL recalled user name (response: )
|
||||||
|
OK no agent identity on headless run
|
||||||
|
|
||||||
|
selftest: 26 passed, 2 failed
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
cand node-tasks exit 0 load 5.91
|
||||||
|
cand node-bus exit 0 load 5.92
|
||||||
|
cand node-business exit 0 load 5.92
|
||||||
|
cand node-discord exit 0 load 5.92
|
||||||
|
cand suite-auth exit 0 load 5.92
|
||||||
|
cand suite-conductor exit 0 load 5.93
|
||||||
|
cand suite-config exit 0 load 5.78
|
||||||
|
cand suite-discord exit 0 load 5.79
|
||||||
|
cand suite-extension-package exit 0 load 5.79
|
||||||
|
cand suite-foundation exit 0 load 5.44
|
||||||
|
cand suite-queue exit 0 load 5.77
|
||||||
|
cand suite-release exit 0 load 5.77
|
||||||
|
cand suite-task exit 1 load 5.77
|
||||||
|
base node-bus exit 0 load 5.78
|
||||||
|
base node-business exit 0 load 5.78
|
||||||
|
base node-discord exit 0 load 8.68
|
||||||
|
base suite-auth exit 0 load 8.68
|
||||||
|
base suite-conductor exit 0 load 8.26
|
||||||
|
base suite-config exit 0 load 8.26
|
||||||
|
base suite-discord exit 0 load 7.76
|
||||||
|
base suite-extension-package exit 0 load 7.46
|
||||||
|
base suite-foundation exit 0 load 5.74
|
||||||
|
base suite-queue exit 0 load 6.69
|
||||||
|
base suite-release exit 0 load 6.69
|
||||||
|
base suite-task exit 1 load 6.69
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
V1 killed (2)
|
||||||
|
V2 killed (1)
|
||||||
|
V3 killed (1)
|
||||||
|
V4 killed (1)
|
||||||
|
V5 killed (1)
|
||||||
|
V6 killed (1)
|
||||||
|
V7 killed (2)
|
||||||
|
V8 killed (2)
|
||||||
|
V9 killed (2)
|
||||||
|
V10 killed (2)
|
||||||
|
V11 killed (1)
|
||||||
|
V12 SURVIVED
|
||||||
|
V13 killed (1)
|
||||||
|
V14 killed (1)
|
||||||
|
V15 killed (1)
|
||||||
|
V16 killed (1)
|
||||||
|
V17 killed (1)
|
||||||
|
V18 killed (1)
|
||||||
|
V19 SURVIVED
|
||||||
|
V20 SURVIVED
|
||||||
|
V21 killed (1)
|
||||||
|
V22 killed (1)
|
||||||
|
V23 SURVIVED
|
||||||
|
V24 SURVIVED
|
||||||
|
S1 killed (1)
|
||||||
|
S2 killed (8)
|
||||||
|
S3 SURVIVED
|
||||||
|
S4 killed (2)
|
||||||
|
S5 killed (1)
|
||||||
|
S6 SURVIVED
|
||||||
|
S7 SURVIVED
|
||||||
|
S8 killed (1)
|
||||||
|
S9 SURVIVED
|
||||||
|
S10 SURVIVED
|
||||||
|
S11 killed (1)
|
||||||
|
U1 killed (1)
|
||||||
|
U2 killed (3)
|
||||||
|
U3 killed (1)
|
||||||
|
U4 killed (2)
|
||||||
|
U5 SURVIVED
|
||||||
|
U6 SURVIVED
|
||||||
|
U7 SURVIVED
|
||||||
|
U8 killed (1)
|
||||||
|
A1 killed (1)
|
||||||
|
A2 killed (1)
|
||||||
|
A3 killed (2)
|
||||||
|
A4 killed (1)
|
||||||
|
A5 killed (1)
|
||||||
|
A6 killed (1)
|
||||||
|
K1 SURVIVED
|
||||||
|
K2 killed (1)
|
||||||
|
K3 SURVIVED
|
||||||
|
K4 killed (5)
|
||||||
|
G1 killed (2)
|
||||||
|
G2 SURVIVED
|
||||||
|
B1 killed (1)
|
||||||
|
B2 killed (1)
|
||||||
|
B3 killed (1)
|
||||||
|
B4 killed (1)
|
||||||
|
B5 killed (1)
|
||||||
|
B6 killed (1)
|
||||||
|
B7 killed (1)
|
||||||
|
B8 SURVIVED
|
||||||
|
N1 killed (1)
|
||||||
|
N2 killed (1)
|
||||||
|
N3 SURVIVED
|
||||||
|
N4 killed (1)
|
||||||
|
N5 SURVIVED
|
||||||
|
N6 killed (1)
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
v24.21.0
|
||||||
|
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (156.683815ms)
|
||||||
|
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (294.274172ms)
|
||||||
|
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (285.287143ms)
|
||||||
|
✔ launch events require a human CLI capability; generic emit cannot forge authority events (201.828825ms)
|
||||||
|
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (266.798953ms)
|
||||||
|
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (255.797092ms)
|
||||||
|
✔ task action subjects and linked decision trail are complete and ordered (255.175739ms)
|
||||||
|
✔ launch binding is durable and reconnecting requires the identical trusted record (153.48265ms)
|
||||||
|
✔ business isolation includes inherited object names and cross-business message references (261.614001ms)
|
||||||
|
✔ authority never transfers between action, run, target, unresolved or replaced role holder (499.890688ms)
|
||||||
|
✔ task projection uses schema current view, skipping earlier and equal-start polls (220.584023ms)
|
||||||
|
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (277.668609ms)
|
||||||
|
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (190.09813ms)
|
||||||
|
✔ both arbiters require human resolution when their cross-role route is themselves (274.3981ms)
|
||||||
|
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (5.738838ms)
|
||||||
|
✔ only validated broker references load; returned data and exceptions cannot expose a known token (13.669289ms)
|
||||||
|
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (3.074295ms)
|
||||||
|
✔ expiry refuses use and env references never become client data (0.962253ms)
|
||||||
|
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (2.044467ms)
|
||||||
|
✔ opaque tokens shorter than 16 characters refuse before use (0.646557ms)
|
||||||
|
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (4.189933ms)
|
||||||
|
✔ process reader gets own kernel identity without exposing environment values (0.819456ms)
|
||||||
|
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (5.21161ms)
|
||||||
|
✔ real pid 1 remains inspectable when its environment is protected (0.507027ms)
|
||||||
|
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (198.358392ms)
|
||||||
|
✔ missing and foreign-business citations refuse and roll back message and grant (214.848463ms)
|
||||||
|
✔ cross-role sends still need a matching resolved decision and consume it once (253.418804ms)
|
||||||
|
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (243.648194ms)
|
||||||
|
✔ startup token refusal returns safe code without value or partial listening broker (47.012202ms)
|
||||||
|
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (191.423851ms)
|
||||||
|
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (189.913075ms)
|
||||||
|
✔ trusted host registers later launches; socket clients never have a registration verb (179.451682ms)
|
||||||
|
✔ runtime excludes declared project roots even when host supplies no repoRoots (35.026481ms)
|
||||||
|
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (209.669565ms)
|
||||||
|
✔ v3b prototype refusals, views and append-only mutations (1141.755297ms)
|
||||||
|
✔ gated approval authorizes once, survives store reopen, and fresh approval works (264.541696ms)
|
||||||
|
✔ another run cannot consume an approval; a failed check leaves it usable (238.367622ms)
|
||||||
|
✔ two scheduled callers have exactly one grant and one consumed refusal (178.480934ms)
|
||||||
|
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (378.072645ms)
|
||||||
|
✔ class drift gated to cross-role refuses before consumption (364.594342ms)
|
||||||
|
✔ class drift cross-role to gated refuses before consumption (293.318848ms)
|
||||||
|
✔ class drift gated to within-role refuses before consumption (319.843551ms)
|
||||||
|
✔ class drift cross-role to within-role refuses before consumption (390.074511ms)
|
||||||
|
✔ class drift within-role to gated refuses before consumption (302.392116ms)
|
||||||
|
✔ class drift within-role to cross-role refuses before consumption (266.74969ms)
|
||||||
|
✔ message.send consumes approval and prevents a later send or authorize (290.653606ms)
|
||||||
|
✔ role.revoke consumes approval and prevents a later revoke or authorize (287.370151ms)
|
||||||
|
✔ creates private WAL store and excludes a second writer until explicit close (123.91485ms)
|
||||||
|
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (214.167415ms)
|
||||||
|
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (203.524319ms)
|
||||||
|
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (200.118618ms)
|
||||||
|
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (106.76254ms)
|
||||||
|
✔ async transactions refuse before invoking their function (109.987831ms)
|
||||||
|
✔ recordTask keeps sync reads and a role write apart (181.34273ms)
|
||||||
|
✔ read_at must be one canonical UTC format, so the projection compares strings safely (124.834991ms)
|
||||||
|
✔ a bad entry refuses the whole record (135.312373ms)
|
||||||
|
✔ taskView reads the projection for one business (131.329473ms)
|
||||||
|
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (251.724193ms)
|
||||||
|
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (446.554972ms)
|
||||||
|
✔ without an adapter the server refuses every task verb (281.70093ms)
|
||||||
|
✔ the runtime refuses an invalid adapter and closes a valid one (295.177841ms)
|
||||||
|
✔ the process loads the S3 adapter from plain-data trackers (383.983423ms)
|
||||||
|
✔ socket capability stamps launch identity; shared views use wire, no SQL client (162.358648ms)
|
||||||
|
✔ two wire claims serialize; a lost reply never automatically retries (203.497931ms)
|
||||||
|
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (127.470225ms)
|
||||||
|
✔ client preserves UTF-8 when a response divides a multibyte character (15.892863ms)
|
||||||
|
✔ committed mutation followed by dropped reply reports unknown and is never retried (152.138594ms)
|
||||||
|
✔ the boot config is checked before anything starts (114.446203ms)
|
||||||
|
✔ a business with no tracker entry refuses task verbs (165.570715ms)
|
||||||
|
✔ credential.expiring and .expired are recorded once per instance (277.869743ms)
|
||||||
|
✔ a token file that changes on disk records credential.changed (126.831369ms)
|
||||||
|
✔ autostart polls, reconciles and retries a startup the tracker was down for (142.210614ms)
|
||||||
|
✔ a refusal a restart must clear is not retried by the poll (113.521601ms)
|
||||||
|
✔ a poll that fires while two are queued is dropped (207.621166ms)
|
||||||
|
✔ close waits for a running verb and refuses one that has not started (304.784433ms)
|
||||||
|
✔ the bundled Vikunja is the pinned upstream image the runbook names (1.562596ms)
|
||||||
|
✔ every published port is on 127.0.0.1, and no secret is in the file (0.582587ms)
|
||||||
|
✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (464.901892ms)
|
||||||
|
✔ the recorded task bodies pass the checks S3 applies to every read (0.713834ms)
|
||||||
|
✔ the client works against the fake over real HTTP with the platform fetch (251.982965ms)
|
||||||
|
✔ a correct install starts, and the first reconcile records tasks that already exist (155.528439ms)
|
||||||
|
✔ verbs refuse while a business is starting and after startup refused it (172.948478ms)
|
||||||
|
✔ startup refuses a token that can do more than its role needs (486.365623ms)
|
||||||
|
✔ startup refuses an unsupported version and flags an untested one (460.393023ms)
|
||||||
|
✔ startup refuses a board that the runbook did not install (548.890888ms)
|
||||||
|
✔ startup refuses a project the sync bot cannot read (123.186793ms)
|
||||||
|
✔ startup refuses a configured label the pm bot cannot see (154.422859ms)
|
||||||
|
✔ startup refuses an expired credential and a missing sync credential (418.141481ms)
|
||||||
|
✔ an unreachable tracker refuses with tracker-unavailable (114.527738ms)
|
||||||
|
✔ an edit in the UI is recorded once, with the fields that changed (241.788096ms)
|
||||||
|
✔ a move between open buckets is seen on the board, though updated does not change (211.515078ms)
|
||||||
|
✔ a person's comment is counted and a bot's is not (314.060808ms)
|
||||||
|
✔ the hourly reconcile catches a comment through comment_count (321.406666ms)
|
||||||
|
✔ a task closed in the UI leaves the open view with its done bucket (523.66154ms)
|
||||||
|
✔ a task that leaves the board is recorded as deleted, moved or out of reach (374.270576ms)
|
||||||
|
✔ a poll that read before a verb wrote does not overwrite the verb (315.101943ms)
|
||||||
|
✔ a tracker fault during a tick is reported and the next tick catches up (278.236767ms)
|
||||||
|
✔ a malformed answer refuses the tick with tracker-shape (245.934902ms)
|
||||||
|
✔ no token value reaches the database, the log or a refusal (387.342385ms)
|
||||||
|
✔ the first look at a task counts only comments inside the window (221.67746ms)
|
||||||
|
✔ task.create needs a recorded human request and a requirement id (243.360663ms)
|
||||||
|
✔ only labels named in the business file can be written (262.314908ms)
|
||||||
|
✔ task.schedule sets and clears a due date and relations (323.01973ms)
|
||||||
|
✔ assign and reassign move the role bots and record task.assigned (494.875301ms)
|
||||||
|
✔ task.update.assigned is for the assignee and records task.state (402.075606ms)
|
||||||
|
✔ a wrong expected digest records task.conflict and writes nothing (298.001411ms)
|
||||||
|
✔ a cross-role verb needs a resolved decision, used once (492.684567ms)
|
||||||
|
✔ task.close needs a verdict; after it every verb refuses with task-done (362.762253ms)
|
||||||
|
✔ a lost answer is settled by a re-read and never retried (359.022185ms)
|
||||||
|
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (247.871317ms)
|
||||||
|
✔ a task the sync bot cannot read refuses and records nothing (96.987524ms)
|
||||||
|
✔ verbs and polls for one business run one at a time (256.240989ms)
|
||||||
|
✔ a due date with milliseconds is written to the second (159.84818ms)
|
||||||
|
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (223.361496ms)
|
||||||
|
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (167.657207ms)
|
||||||
|
✔ a create whose final read fails succeeds and records task.created (186.75213ms)
|
||||||
|
✔ an edit between the last write and the final read shows as external on the next poll (126.50278ms)
|
||||||
|
✔ task.created is recorded when a later label write fails (129.164131ms)
|
||||||
|
ℹ tests 118
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 118
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 5049.292544
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
D1 self snapshot due_date self 2026-12-01T09:00:00.000Z returned digest 1c30ad1187e1
|
||||||
|
D1 external events after one tick []
|
||||||
|
D1 same schedule again sends PATCH 0
|
||||||
|
D1 expect=returned digest -> decision-not-found
|
||||||
|
D2 external events 0
|
||||||
|
W1 caller sees ok
|
||||||
|
W1 coder assigned in tracker true
|
||||||
|
W1 action.allowed task.assign 1
|
||||||
|
W1 task.assigned events 1 self snapshots 2
|
||||||
|
W1 poll external events []
|
||||||
|
W1 caller retries assign -> task-assigned
|
||||||
|
H1 human task.assign agent-required
|
||||||
|
H2 reader task.assign read-only
|
||||||
|
H3 non-holder coder update not-holder
|
||||||
|
R1 other task id 2 schedule -> ok relations on task 1 [{"kind":"related","other":2}]
|
||||||
|
C1 reviewer (no scope.change authority) wrong expect -> task-conflict task.conflict events 1
|
||||||
|
C1 reviewer right digest -> decision-required
|
||||||
|
M1 tick -> tracker-unavailable external 0 missing 0
|
||||||
|
T1 next tick -> ok external 0 missing 1
|
||||||
|
P1 create -> vikunja:1/2 snapshot self labels [1] due 2026-11-01T10:20:30.000Z digest==returned true task.created 2
|
||||||
|
✔ D1 schedule with milliseconds: the poll reports the bot's own due date as an external change (119.739669ms)
|
||||||
|
✔ D2 whole seconds: no external change (control) (83.77551ms)
|
||||||
|
✔ W1 write lands, final read fails: refusal, no self record, poll calls it external (90.552022ms)
|
||||||
|
✔ H1 a human, H2 a reader, and a non-holder cannot call a task verb (86.12839ms)
|
||||||
|
✔ R1 a relation to another project's task id under this project's ref (81.923787ms)
|
||||||
|
✔ C1 a conflict event costs no authority; a role without the verb can still write task.conflict (86.879606ms)
|
||||||
|
✔ M1 one task with a 500 in missing() stalls the tick; T1 the tick after recovers (79.761787ms)
|
||||||
|
P1 poll external events for the new task []
|
||||||
|
P2 caller sees ok state 3 due in tracker 2026-12-02T00:00:00Z schedule events 0 last snapshot self 2026-12-02T00:00:00.000Z
|
||||||
|
P3 caller sees ok PATCHes sent 0
|
||||||
|
✔ P1 create with a label and a relation, final read fails: snapshot matches the poll (104.489902ms)
|
||||||
|
✔ P2 step settled by re-read, then the final read fails: success (126.271579ms)
|
||||||
|
✔ P3 nothing to write, final read fails (78.746838ms)
|
||||||
|
ℹ tests 10
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 10
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 1010.940239
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
kanban view 24 default_bucket_id 16 done_bucket_id 18
|
||||||
|
plain digest equal true differing [] updated answer 2026-10-09T00:32:29.621Z read 2026-10-09T00:32:29.000Z answer raw "2026-10-09T00:32:29.621574557Z" read raw "2026-10-09T00:32:29Z"
|
||||||
|
placed in bucket 16 default 16
|
||||||
|
full digest equal true differing [] updated answer 2026-10-09T00:32:29.639Z read 2026-10-09T00:32:29.000Z answer raw "2026-10-09T00:32:29.639168224Z" read raw "2026-10-09T00:32:29Z"
|
||||||
|
placed in bucket 16 default 16
|
||||||
|
labels+relation digest equal true differing [] updated answer 2026-10-09T00:32:29.651Z read 2026-10-09T00:32:29.000Z answer raw "2026-10-09T00:32:29.651562633Z" read raw "2026-10-09T00:32:29Z"
|
||||||
|
placed in bucket 16 default 16
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
# Slice 1 S3, row 38, round 2 review (Filbert)
|
||||||
|
|
||||||
|
Issue #1520, request comment 26852, queue rev 186. Sage's request
|
||||||
|
2026-10-09T00:28:36Z. Packet: `agents/darkwing/work/slice1-s3/` at
|
||||||
|
`b144a03d`. Candidate: manifest sha256
|
||||||
|
`e10e30e3d68c12177afba438ac02a06c208ea0370c7198af1f9d9cf163d637f4`,
|
||||||
|
28 files, over `c9ef7ee4` with `build-r2.patch` (sha256
|
||||||
|
`71ce87e631b04d35591e2df966aae565d5f665ac2a9d5d252322ce1b0d5be69c`).
|
||||||
|
Round 1: `review-r1.md` here.
|
||||||
|
|
||||||
|
Verdict: **approve.** Both round 1 blockers are fixed and tested, and so
|
||||||
|
are the three asked-for tests. I checked create()'s new final-read
|
||||||
|
fallback against the pinned Vikunja image: its snapshot digests the same
|
||||||
|
as a read, and the task lands in the default bucket. Sage's landing
|
||||||
|
condition stands: Darkwing's test-release and test-task reruns after the
|
||||||
|
zai reset. My gate didn't exercise the real model turns (see Suites), so
|
||||||
|
this approval doesn't cover them.
|
||||||
|
|
||||||
|
The notes below are small. None of them blocks.
|
||||||
|
|
||||||
|
## Method
|
||||||
|
|
||||||
|
- Detached worktrees at `c9ef7ee4` under `~/filbert-scratch/r38b/`. In
|
||||||
|
the candidate I ran `git apply build-r2.patch` and then `sha256sum -c`:
|
||||||
|
28 OK. After the mutant run the tree checks clean against the manifest
|
||||||
|
again. A third worktree held the round 1 candidate. My own tree diff
|
||||||
|
between the two candidates shows the same five files as Darkwing's
|
||||||
|
`r1-to-r2.diff`, all under `packages/tasks`, and the same 335 lines.
|
||||||
|
- `gate.sh` runs the suites one at a time in both trees and tees each
|
||||||
|
output. As in round 1, `DOCKER_HOST` points at a socket that doesn't
|
||||||
|
exist, so no model turns run.
|
||||||
|
- `probe.test.mjs` holds the round 1 probes, with D1 now expecting the
|
||||||
|
fix. It adds P1 to P3 for the new fallbacks.
|
||||||
|
- `vkcreate.mjs` runs against a scratch Vikunja on the pinned image
|
||||||
|
(`vikunja/vikunja@sha256:e2204a1c…cfc`, v2.7.0) on 127.0.0.1, on the
|
||||||
|
default bridge, with a throwaway user. It compares create()'s fallback
|
||||||
|
fields with a read. The container and its data are removed.
|
||||||
|
- `mutants.sh` runs the 63 round 1 mutants plus N1 to N6, which target
|
||||||
|
the round 2 code.
|
||||||
|
- Node 24 run: `node:24` with no network, the tree mounted read-only, and
|
||||||
|
the host uid.
|
||||||
|
|
||||||
|
## Suites
|
||||||
|
|
||||||
|
| Suite | Candidate | Base |
|
||||||
|
|---|---|---|
|
||||||
|
| node tasks (Node 26.8.1) | 51/51 | n/a |
|
||||||
|
| node tasks + bus (Node 24.21.0) | 118/118 | n/a |
|
||||||
|
| node bus | 67/67 | 58/58 |
|
||||||
|
| node business | 60/60 | 60/60 |
|
||||||
|
| node discord | 173/173 | 173/173 |
|
||||||
|
| test-auth | 15/15 | 15/15 |
|
||||||
|
| test-conductor | 17/17 | 17/17 |
|
||||||
|
| test-config | 24/24 | 24/24 |
|
||||||
|
| test-discord | 64/64 | 64/64 |
|
||||||
|
| test-extension-package | 18/18 | 18/18 |
|
||||||
|
| test-foundation | 44/44 | 44/44 |
|
||||||
|
| test-queue | 27/27 | 27/27 |
|
||||||
|
| test-release | 4/4 | 4/4 |
|
||||||
|
| test-task | 26 pass, 2 fail | 26 pass, 2 fail |
|
||||||
|
|
||||||
|
Base and candidate fail the same two test-task cases, "user recall run
|
||||||
|
succeeds" and "recalled user name", as in round 1 and row 39. With no
|
||||||
|
Docker socket, test-release runs 4 cases and test-task 28, so the real
|
||||||
|
model turns that hit Darkwing's zai limit never ran here. I hit no 429
|
||||||
|
and no address-pool error. Neither suite loads `packages/tasks` or
|
||||||
|
`packages/bus`, so I don't count Darkwing's 3 and 8 failures against the
|
||||||
|
candidate. The reruns Sage asked for are still the landing condition.
|
||||||
|
|
||||||
|
## B1: fixed
|
||||||
|
|
||||||
|
`due()` drops the milliseconds before the write, and `FakeVikunja` now
|
||||||
|
stores due dates to the second and echoes what it was sent, as v2.7.0
|
||||||
|
does (`r1-vk-due-probe.txt`). Probe D1, which showed the bug in round 1,
|
||||||
|
now gives:
|
||||||
|
|
||||||
|
```
|
||||||
|
D1 self snapshot due_date self 2026-12-01T09:00:00.000Z
|
||||||
|
D1 external events after one tick []
|
||||||
|
D1 same schedule again sends PATCH 0
|
||||||
|
D1 expect=returned digest -> decision-not-found
|
||||||
|
```
|
||||||
|
|
||||||
|
The last line passes the `expect` check. It then refuses only because the
|
||||||
|
probe names a decision that doesn't exist. In round 1 it refused
|
||||||
|
`task-conflict`. The new test "a due date with milliseconds is written to
|
||||||
|
the second" covers create, schedule, a repeated schedule, `expect`, and a
|
||||||
|
PATCH settled by re-read. Mutant N4, which removes the truncation, is
|
||||||
|
killed. Mutant G1, which survived in round 1, is now killed too. The
|
||||||
|
README states the one-second precision with an example.
|
||||||
|
|
||||||
|
## B2: fixed
|
||||||
|
|
||||||
|
Probe W1 from round 1 now gives:
|
||||||
|
|
||||||
|
```
|
||||||
|
W1 caller sees ok
|
||||||
|
W1 task.assigned events 1 self snapshots 2
|
||||||
|
W1 poll external events []
|
||||||
|
W1 caller retries assign -> task-assigned
|
||||||
|
```
|
||||||
|
|
||||||
|
I checked the three branches in `handle()`:
|
||||||
|
|
||||||
|
- **Every write landed, read failed.** The verb succeeds and records the
|
||||||
|
event and a snapshot of `work.expect(before.fields)`. That snapshot has
|
||||||
|
`etag` null and `updated` taken from the compare-read.
|
||||||
|
- An older `updated` is the safe direction.
|
||||||
|
`task_external_changes` requires `p.updated >= ls.updated`, so a later
|
||||||
|
poll still qualifies.
|
||||||
|
- The staleness check in `consider()` and `missing()` uses the
|
||||||
|
snapshot's `at`, not `updated`.
|
||||||
|
- **Some landed, read failed.** The verb refuses `write-uncertain` and
|
||||||
|
records nothing. `landed` counts a step that `step()` settled by
|
||||||
|
re-read.
|
||||||
|
- Probe P2 shows that: a PATCH that lands but answers 500, settled by
|
||||||
|
the re-read, then the final read fails. The verb returns ok.
|
||||||
|
- Mutants N1 (always throw the step's own code) and N2 (no `landed++`)
|
||||||
|
are both killed.
|
||||||
|
- **None landed, read failed.** The failed write's own code. A verb with
|
||||||
|
nothing to write and a failed final read also succeeds, with no PATCH
|
||||||
|
sent (probe P3). That is correct, because nothing needed writing.
|
||||||
|
|
||||||
|
## create(): same treatment, checked
|
||||||
|
|
||||||
|
When every label and relation write landed and the final read fails,
|
||||||
|
create() records `task.created` and a snapshot built from the create's
|
||||||
|
answer plus the labels it wrote, in the default bucket. It then returns
|
||||||
|
success. Against the pinned image (`r2-vk-create-probe.txt`), for a plain
|
||||||
|
task, a full one (description, due date, priority) and one with two
|
||||||
|
labels added out of id order and a relation:
|
||||||
|
|
||||||
|
```
|
||||||
|
plain digest equal true differing []
|
||||||
|
full digest equal true differing []
|
||||||
|
labels+relation digest equal true differing []
|
||||||
|
placed in bucket 16 default 16 (each case)
|
||||||
|
```
|
||||||
|
|
||||||
|
The fallback fields match a read in every digested field. The task sits
|
||||||
|
in the view's default bucket, which startup already checks equals
|
||||||
|
`todo`. In the fake, probe P1 creates with a label and a relation, faults
|
||||||
|
the final read, and then ticks: no external event, and the snapshot
|
||||||
|
digest equals the one returned. Mutant N6, which removes the fallback, is
|
||||||
|
killed.
|
||||||
|
|
||||||
|
## Notes (not blocking)
|
||||||
|
|
||||||
|
1. **The create fallback's `updated` is finer than a read's.** The
|
||||||
|
create's answer carries nanoseconds (`2026-10-09T00:32:29.621574557Z`),
|
||||||
|
which `normal()` keeps as `.621Z`. A read gives `00:32:29Z`.
|
||||||
|
- So the fallback snapshot's `updated` can be up to 999 ms ahead of the
|
||||||
|
next poll's for the same version.
|
||||||
|
- `task_external_changes` requires `p.updated >= ls.updated`. A
|
||||||
|
person's edit in the same wall second as the create would therefore
|
||||||
|
drop out of that view.
|
||||||
|
- The `task.changed.external` event is still recorded, because
|
||||||
|
`consider()` compares digests.
|
||||||
|
- This needs a failed final read and an edit within the second. Nothing
|
||||||
|
reads the view yet. Flooring the fallback `updated` to the second
|
||||||
|
(`sync.mjs` already has `floorSecond`) would close it.
|
||||||
|
2. **Mutant N3 survives.** It drops the `labels` override in the create
|
||||||
|
fallback. Darkwing's create test faults the final read on a create
|
||||||
|
with no labels. My P1 would kill N3; adding `labels: [w.label]` to that
|
||||||
|
test would too.
|
||||||
|
3. **Mutant N5 survives.** It throws on any step failure before
|
||||||
|
recording, so a failed step with a good final read records no
|
||||||
|
snapshot. Round 1 had the same branch, and no test checks that
|
||||||
|
snapshot. One test with a refused second step and an assertion on the
|
||||||
|
last snapshot would hold it.
|
||||||
|
4. **README, create bullet.** It says the verb succeeds. The fallback
|
||||||
|
applies only when every label and relation write landed. If one
|
||||||
|
failed and the final read failed too, `task.created` is recorded with
|
||||||
|
no snapshot, and the step's refusal is thrown. The code comment says
|
||||||
|
this; the README bullet doesn't.
|
||||||
|
5. **The returned `updated`** on the read-failed success path is the
|
||||||
|
compare-read's, older than the write. The README says so.
|
||||||
|
6. Round 1 notes 1 to 14 still apply where they weren't fixed. Darkwing
|
||||||
|
has deferred note 7 (`secretCheck` on the result) to follow-ups. I
|
||||||
|
agree it stays out of this row.
|
||||||
|
|
||||||
|
## Mutants
|
||||||
|
|
||||||
|
46 of the 63 round 1 mutants are killed. 17 survive, which matches
|
||||||
|
Darkwing's count. V9, V21, S5 and G1 moved from survived to killed. Every
|
||||||
|
other result is unchanged from round 1, and the reasons are in
|
||||||
|
`review-r1.md`. Of the round 2 mutants, four of six are killed.
|
||||||
|
|
||||||
|
| Mutant | Result |
|
||||||
|
|---|---|
|
||||||
|
| V9 success snapshot from the final read | killed (T1 test) |
|
||||||
|
| V21 no `task.created` after a failed step | killed (T2 test) |
|
||||||
|
| S5 first look counts every comment | killed (T3 test) |
|
||||||
|
| G1 due date not normalized | killed (B1 test) |
|
||||||
|
| V12 V19 V20 V23 V24 S3 S6 S7 S9 S10 U5 U6 U7 K1 K3 G2 B8 | survived, as in round 1 |
|
||||||
|
| N1 partial landing throws the step's code | killed |
|
||||||
|
| N2 settled steps not counted as landed | killed |
|
||||||
|
| N3 create fallback without the labels written | **survived** (note 2) |
|
||||||
|
| N4 due date not truncated | killed |
|
||||||
|
| N5 a failed step records nothing even when the read worked | **survived** (note 3) |
|
||||||
|
| N6 no create fallback | killed |
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `probe.test.mjs`, `vkcreate.mjs`, `mutants.sh`, `gate.sh` (round 2
|
||||||
|
versions; the round 1 scripts were replaced in place, and `review-r1.md`
|
||||||
|
records their results)
|
||||||
|
- Output:
|
||||||
|
- `r2-probe.txt`
|
||||||
|
- `r2-vk-create-probe.txt`
|
||||||
|
- `r2-mut-summary.txt`
|
||||||
|
- `r2-node24.txt`
|
||||||
|
- `r2-gate-summary.txt`
|
||||||
|
- `r2-cand-*.txt` and `r2-base-*.txt` (each suite, teed)
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// Filbert, row 38 r2: does create()'s final-read fallback (the create answer plus the labels
|
||||||
|
// written, in the default bucket) digest the same as a read on Vikunja v2.7.0? Scratch container on
|
||||||
|
// 127.0.0.1 only. The password and token stay in memory; nothing here prints them.
|
||||||
|
// Usage: node vkcreate.mjs <origin> <candidate root>
|
||||||
|
import { randomBytes } from 'node:crypto';
|
||||||
|
const ORIGIN = process.argv[2];
|
||||||
|
if (!/^http:\/\/127\.0\.0\.1:\d+$/.test(ORIGIN)) throw new Error('loopback only');
|
||||||
|
const { taskFields, digest } = await import(process.argv[3] + '/packages/tasks/src/digest.mjs');
|
||||||
|
const { normal } = await import(process.argv[3] + '/packages/tasks/src/sync.mjs');
|
||||||
|
const BASE = ORIGIN + '/api/v2';
|
||||||
|
async function api(method, path, body, auth) {
|
||||||
|
const headers = { 'Content-Type': 'application/json' };
|
||||||
|
if (auth) headers.Authorization = `Bearer ${auth}`;
|
||||||
|
const r = await fetch(BASE + path, { method, headers, body: body === undefined ? undefined : JSON.stringify(body) });
|
||||||
|
const t = await r.text();
|
||||||
|
let json = null;
|
||||||
|
try { json = JSON.parse(t); } catch {}
|
||||||
|
return { status: r.status, json };
|
||||||
|
}
|
||||||
|
const must = (r, l) => { if (r.status >= 300) throw new Error(`${l}: ${r.status} ${JSON.stringify(r.json)}`); return r.json; };
|
||||||
|
const user = 'fp' + randomBytes(4).toString('hex');
|
||||||
|
const password = randomBytes(18).toString('base64url');
|
||||||
|
must(await api('POST', '/register', { username: user, email: user + '@example.invalid', password }), 'register');
|
||||||
|
const O = must(await api('POST', '/login', { username: user, password }), 'login').token;
|
||||||
|
const P = must(await api('POST', '/projects', { title: 'probe' }, O), 'project').id;
|
||||||
|
const views = must(await api('GET', `/projects/${P}/views`, undefined, O), 'views'); const vl = views.items ?? views;
|
||||||
|
const kanban = vl.find((v) => v.view_kind === 'kanban');
|
||||||
|
console.log('kanban view', kanban.id, 'default_bucket_id', kanban.default_bucket_id, 'done_bucket_id', kanban.done_bucket_id);
|
||||||
|
const L1 = must(await api('POST', '/labels', { title: 'b' }, O), 'label').id;
|
||||||
|
const L2 = must(await api('POST', '/labels', { title: 'a' }, O), 'label').id;
|
||||||
|
const other = must(await api('POST', `/projects/${P}/tasks`, { title: 'other' }, O), 'other').id;
|
||||||
|
const cases = [
|
||||||
|
{ title: 'plain' },
|
||||||
|
{ title: 'full', description: '<p>d</p>', due_date: '2026-11-01T10:20:30.000Z', priority: 3 },
|
||||||
|
{ title: 'labels+relation', due_date: '2026-11-01T10:20:30.000Z', labels: [L1, L2], relation: true },
|
||||||
|
];
|
||||||
|
for (const c of cases) {
|
||||||
|
const { labels = [], relation, ...body } = c;
|
||||||
|
const r = await api('POST', `/projects/${P}/tasks`, body, O);
|
||||||
|
must(r, 'create');
|
||||||
|
for (const l of [...labels].reverse()) must(await api('POST', `/tasks/${r.json.id}/labels`, { label_id: l }, O), 'label');
|
||||||
|
if (relation) must(await api('POST', `/tasks/${r.json.id}/relations`, { other_task_id: other, relation_kind: 'related' }, O), 'relation');
|
||||||
|
const g = must(await api('GET', `/tasks/${r.json.id}`, undefined, O), 'read');
|
||||||
|
const B = kanban.default_bucket_id;
|
||||||
|
const fb = { ...taskFields(r.json, B), labels: [...labels].sort((a, b) => a - b) };
|
||||||
|
const rd = taskFields(g, B);
|
||||||
|
const diff = Object.keys(rd).filter((k) => JSON.stringify(rd[k]) !== JSON.stringify(fb[k]));
|
||||||
|
console.log(c.title.padEnd(16), 'digest equal', digest(fb) === digest(rd), 'differing', JSON.stringify(diff),
|
||||||
|
'updated answer', normal(r.json.updated), 'read', normal(g.updated), 'answer raw', JSON.stringify(r.json.updated), 'read raw', JSON.stringify(g.updated));
|
||||||
|
if (diff.length) console.log(' fallback', JSON.stringify(diff.map((k) => fb[k])), 'read', JSON.stringify(diff.map((k) => rd[k])));
|
||||||
|
const bt = must(await api('GET', `/projects/${P}/views/${kanban.id}/buckets/tasks`, undefined, O), 'buckets');
|
||||||
|
const where = (bt.items ?? bt).find((b) => (b.tasks ?? []).some((t) => t.id === r.json.id));
|
||||||
|
console.log(' placed in bucket', where?.id, 'default', B);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user