work: Rocko row 114 round 1 packet (#1608), candidate manifest 68c60a799146
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
# Row 114 — Discord connector unit on a pinned copy (#1608)
|
||||
|
||||
Rocko, GPT-6.1-Sol. Reviewer Filbert. Base 72c9ac36c, after row108's
|
||||
landing (base.txt). Detached /mnt/storage/src/mosaic-stack-rocko-114.
|
||||
Started rev1185; Sage assigned sage-0228 and granted slot sage-0229.
|
||||
No candidate code commit, push or live Discord action.
|
||||
|
||||
## Implementation and choices
|
||||
|
||||
Service render/install --copy selects a full 40-character lowercase id
|
||||
under the shared XDG_DATA_HOME/mosaic-stack/copies location or --copies.
|
||||
A missing CLI or pi refuses exit1 before unit-directory/temp creation.
|
||||
Explicit empty id is distinct from an omitted flag and also refuses.
|
||||
Copy directory/node paths use the same plain path alphabet as the other
|
||||
service scripts. Without --copy, renderer produces the original checkout
|
||||
bytes; install names the checkout and recommends --copy.
|
||||
|
||||
The template is deliberately untouched. Rendering first substitutes the
|
||||
same original README/PATH/checkout placeholders, then changes only
|
||||
WorkingDirectory and ExecStart when --copy is given. ExecStart calls Node
|
||||
on the copy's CLI directly, --supervised --repo copy. Copies have neither
|
||||
the discord.sh wrapper nor the systemd template, and require neither at
|
||||
runtime. Every other rendered line, including docs, restart/brake/stop,
|
||||
PATH and NoNewPrivileges, remains byte-identical. Test compares all such
|
||||
lines; out/checkout-render-before/after.txt also compare exactly.
|
||||
|
||||
Status reads actual WorkingDirectory from the user manager, prints its tree
|
||||
and copy id or checkout path, including effective drop-ins. It does not
|
||||
infer the tree from an install flag. Tests use a stub manager and isolated
|
||||
HOME/config paths; no real unit/binding/token is read by this build.
|
||||
|
||||
CLI accepts --offline only for check. It executes the original prepare,
|
||||
readToken and local reporting paths, returns before constructing REST and
|
||||
gateway, and ends with an explicit no-network line. The existing local
|
||||
checks include binding, context against --repo, tool roots, pi/token file
|
||||
and journal/STOP reporting. It retains the normal check's diagnostic STOP
|
||||
behavior: reports presence, does not clear STOP/create a journal/claim a
|
||||
run or launch pi. Online check and the run body are unchanged. Run with
|
||||
--offline refuses usage before reading config.
|
||||
|
||||
## Evidence and correction
|
||||
|
||||
New focused tests initially refused the unrecognized flags/missing status.
|
||||
After implementation, the positive offline test's overly plain synthetic
|
||||
token failed the existing token-format check. Changed only fixture content
|
||||
to the existing helper's non-live dummy value; no validation weakening.
|
||||
Five focused tests pass (out/green-final.txt). The offline fixture traps
|
||||
fetch/WebSocket, marks attempted network calls, and proves offline passes
|
||||
without attempts while normal check still reaches REST and is trapped.
|
||||
Bad0644 token mode, missing context under selected repo and run flag misuse
|
||||
are pinned. Copy tests pin identical legacy bytes, unchanged other lines,
|
||||
empty/short/missing id, missing CLI/pi without any unit, and effective cwd.
|
||||
Shell cases also cover temporary --dir --no-reload installs, all required
|
||||
refusals, status and wrapper offline/run misuse.
|
||||
|
||||
The three prescribed mutants run independently in own process groups;
|
||||
timeouts kill the whole group, and source restores in finally. They
|
||||
reintroduce checkout ExecStart, remove pi existence refusal and make offline
|
||||
call getMe. Final gate/count/mutation and process/network receipts follow.
|
||||
DOCKER_HOST=unix:///nonexistent.sock; Docker-dependent cases explicitly skip.
|
||||
|
||||
## Protected live plan — unexecuted
|
||||
|
||||
This patch touches packages/discord; Sage must notify Mos before and after
|
||||
landing, and every intervening Discord landing stays flagged while the
|
||||
live connector runs the checkout. Live switching waits for the SetSpark
|
||||
freeze to lift and Mos's separate window. Sage prepares a pushed copy;
|
||||
Mos's operator runs Node on that copy's cli.mjs check binding --repo copy
|
||||
--offline (the copy has no discord.sh), preserves the prior unit, installs
|
||||
--copy and reloads/restarts only on Mos's go. Binding context.files resolve
|
||||
inside the copy, so uncopied files refuse. Rollback restores the saved unit,
|
||||
daemon-reloads/restarts on Mos's go and keeps both copies. No live check,
|
||||
REST call, unit install/enable/reload/restart or private binding/token access
|
||||
performed; fixtures only. stack-copy.sh/check/COPY_PATHS remain untouched;
|
||||
connector pin is independent. Decision137 doc-only review fixes ship in a
|
||||
separate packet after landing.
|
||||
|
||||
## Final gate
|
||||
|
||||
| Suite | Passed | Failure / coverage |
|
||||
| --- | ---: | --- |
|
||||
| Discord node | 183 | 0 failed, skipped or cancelled |
|
||||
| test-auth.sh | 15 | 0 |
|
||||
| test-conductor.sh | 17 | 0 |
|
||||
| test-config.sh | 24 | 0 |
|
||||
| test-discord.sh | 78 | 0; includes Discord183 |
|
||||
| test-extension-package.sh | 18 | 0 |
|
||||
| test-foundation.sh | 44 | 0; includes foundation node80 |
|
||||
| test-land-work.sh | 84 | 0 |
|
||||
| test-queue.sh | 28 | 0; includes queue node156; canonical-root verify/render skipped |
|
||||
| test-release.sh | 4 | 0; Docker state machine skipped |
|
||||
| test-task.sh | 26 | 0; four Docker blocks skipped |
|
||||
|
||||
Gate ran20:30:20-20:31:48 UTC, all11 steps exit0. Dead Docker socket for
|
||||
all commands; no Docker/provider coverage claimed. Task skips adapter/
|
||||
mission/retry/skills/roles/seat/policy/user injection, workspace/capability,
|
||||
live task/fork and live user recall blocks. No observed recurrence of the
|
||||
reported gate faults. Source hashes and diff/patch checks pass after mutants.
|
||||
|
||||
| Mutant | Exit | Result |
|
||||
| --- | ---: | --- |
|
||||
| checkout-execstart | 1 | killed |
|
||||
| missing-pi-accepted | 1 | killed |
|
||||
| offline-calls-getMe | 1 | killed |
|
||||
|
||||
3/3 prescribed mutants killed, no timeout. All runners exited; process
|
||||
sweep exit0 busy[]. Docker inspect explicitly reported the named network
|
||||
mosaic-stack-rocko-114_default not found. No removal needed. Slot returned
|
||||
20:32:57 UTC; no subsequent tests. Final manifest7/7 verified:
|
||||
68c60a79914692f3f6d4923f7bcff41de109ccca33bf6fba687fb88efa24bcac.
|
||||
|
||||
Claimant in-review move rev1186, round1; expected missing-token request
|
||||
failure rev1187, command exit1 (out/queue-in-review.txt). Sage requests
|
||||
as jarvis, Filbert reviews. No protected live step executed.
|
||||
|
||||
Own logs-only commit: 618d71ebbb48d2a36512f84ca1c29624289a4423. No candidate source commit.
|
||||
@@ -0,0 +1 @@
|
||||
72c9ac36c33ebb43bf0585bc3ae797969f5ae112
|
||||
@@ -0,0 +1,392 @@
|
||||
diff --git a/docs/TOOLS.md b/docs/TOOLS.md
|
||||
index 20be532e9..fc38cefab 100644
|
||||
--- a/docs/TOOLS.md
|
||||
+++ b/docs/TOOLS.md
|
||||
@@ -469,7 +469,19 @@ user entry may carry `channels`, an allowlist of listed channel ids.
|
||||
`scripts/discord-service.sh install` renders and writes the systemd user
|
||||
unit `[email protected]` (one instance per binding, restart on
|
||||
failure, exit 3 never retried, SIGTERM on `systemctl --user stop`, SIGHUP on
|
||||
-`systemctl --user reload`). An optional `tools` key in the binding declares
|
||||
+`systemctl --user reload`). With
|
||||
+`install --copy ID [--copies DIR]`, a full id selects an existing shared
|
||||
+copy holding the Discord CLI and pi; ExecStart runs Node on that CLI with
|
||||
+--repo and WorkingDirectory in the copy. Without --copy the checkout unit
|
||||
+is unchanged. Status names its effective cwd/copy id, including drop-ins.
|
||||
+`check --offline` validates local binding/context/tool roots/pi/token mode
|
||||
+and reports journal/STOP without REST or gateway calls; run refuses this
|
||||
+flag. For a Mos-approved re-pin after the SetSpark freeze: make the copy,
|
||||
+run `node <copy>/packages/discord/src/cli.mjs check <binding> --repo <copy> --offline`,
|
||||
+save the installed unit, install --copy, then restart on Mos's go. Context
|
||||
+files resolve inside the copy and missing files refuse. Rollback restores
|
||||
+the saved unit and daemon-reloads/restarts on Mos's go; keep both copies.
|
||||
+The connector's pin is independent of stack-copy.sh check. An optional `tools` key in the binding declares
|
||||
file roots; pi then runs with its own tools off and the package's
|
||||
extension providing `list_dir`, `read_file` and `search` confined to those
|
||||
roots, plus `write_file` and `edit_file` for roots marked `"write": true`
|
||||
diff --git a/packages/discord/README.md b/packages/discord/README.md
|
||||
index 8f235b556..29b9fc7cc 100644
|
||||
--- a/packages/discord/README.md
|
||||
+++ b/packages/discord/README.md
|
||||
@@ -17,7 +17,7 @@ CHAT-03 conversation controller exists.
|
||||
## Commands
|
||||
|
||||
```
|
||||
-scripts/discord.sh check <binding>
|
||||
+scripts/discord.sh check <binding> [--offline]
|
||||
scripts/discord.sh run <binding> [--supervised]
|
||||
scripts/discord.sh stop <binding>
|
||||
scripts/discord.sh unlock <binding>
|
||||
@@ -34,6 +34,9 @@ scripts/discord-service.sh render | install | uninstall | status <binding>
|
||||
every listed channel over REST; opens one gateway connection, waits for
|
||||
READY, closes it. Nothing is sent to a channel. Close code 4014 is reported
|
||||
as the message-content intent not being granted in the developer portal.
|
||||
+ `check --offline` stops after local binding/context/tool roots/pi/token-mode
|
||||
+ validation and journal/STOP reporting. It makes no REST or gateway call
|
||||
+ and says so in its last line. `run --offline` is a usage error.
|
||||
- `run` refuses when `STOP` exists or an unresolved delivery cannot be
|
||||
reconciled. Otherwise it starts pi, connects, and serves turns until
|
||||
SIGTERM, SIGINT or `stop`. Run it under the service unit below, or by hand
|
||||
@@ -121,6 +124,25 @@ at runtime, as before. Surviving logout and reboot needs
|
||||
`uninstall` refuses while an instance is active. The log goes to journald:
|
||||
`run` writes no message text to stderr, only ids, counts and state.
|
||||
|
||||
+Pinned install: `scripts/discord-service.sh install --copy ID [--copies DIR]`
|
||||
+uses a full 40-character copy id under the shared mosaic-stack/copies
|
||||
+location. It requires the copy's Discord CLI and node_modules/.bin/pi,
|
||||
+runs Node directly on that CLI with --repo and WorkingDirectory set to
|
||||
+the copy, and retains the unit's restart, brake and stop behavior. Without
|
||||
+--copy the unit still runs the checkout; install explicitly recommends
|
||||
+pinning. Status reads effective WorkingDirectory, including drop-ins, and
|
||||
+prints the tree and copy id or checkout path.
|
||||
+
|
||||
+Every live step requires Mos's go after the SetSpark freeze lifts. Re-pin:
|
||||
+make the new pushed copy with stack-copy.sh; run
|
||||
+`node <copy>/packages/discord/src/cli.mjs check <binding> --repo <copy> --offline`;
|
||||
+preserve the old installed unit file; install --copy ID; reload/restart on
|
||||
+Mos's go. The copy has no scripts/discord.sh, so use its CLI directly.
|
||||
+Binding context.files resolve against the copy: a file not copied refuses
|
||||
+at start/check. Rollback restores the saved unit file, daemon-reloads and
|
||||
+restarts only on Mos's go; retain both copies. This connector pin is
|
||||
+independent of the bus/Console/CLI pin and stack-copy.sh check.
|
||||
+
|
||||
## The binding
|
||||
|
||||
Deployment policy for one seat on one server. It carries Discord ids of real
|
||||
diff --git a/packages/discord/src/cli.mjs b/packages/discord/src/cli.mjs
|
||||
index 045f4918e..ca8c8bdde 100755
|
||||
--- a/packages/discord/src/cli.mjs
|
||||
+++ b/packages/discord/src/cli.mjs
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
// Usage:
|
||||
-// mosaic-discord check <binding> [--config PATH] [--repo PATH]
|
||||
+// mosaic-discord check <binding> [--config PATH] [--repo PATH] [--offline]
|
||||
// mosaic-discord run <binding> [--config PATH] [--repo PATH] [--supervised]
|
||||
// mosaic-discord stop <binding> [--config PATH]
|
||||
// mosaic-discord unlock <binding> [--config PATH]
|
||||
@@ -61,7 +61,7 @@ import { createConnector } from "./connector.mjs";
|
||||
import { ensureJournal, requestStop, stopRequested, readPid, stopTarget, writePid, clearPid, unlock, recover, appendReload, BRAKE_EXIT } from "./journal.mjs";
|
||||
|
||||
const USAGE = [
|
||||
- "usage: mosaic-discord check <binding> [--config PATH] [--repo PATH]",
|
||||
+ "usage: mosaic-discord check <binding> [--config PATH] [--repo PATH] [--offline]",
|
||||
" mosaic-discord run <binding> [--config PATH] [--repo PATH] [--supervised]",
|
||||
" mosaic-discord stop <binding> [--config PATH]",
|
||||
" mosaic-discord unlock <binding> [--config PATH]",
|
||||
@@ -70,7 +70,7 @@ const USAGE = [
|
||||
].join("\n");
|
||||
|
||||
function parse(argv) {
|
||||
- const opts = { command: null, binding: null, config: defaultConfigPath(), repo: process.cwd(), supervised: false };
|
||||
+ const opts = { command: null, binding: null, config: defaultConfigPath(), repo: process.cwd(), supervised: false, offline: false };
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const a = argv[i];
|
||||
if (a === "--config" || a === "--repo") {
|
||||
@@ -80,6 +80,8 @@ function parse(argv) {
|
||||
opts.command = "help";
|
||||
} else if (a === "--supervised") {
|
||||
opts.supervised = true;
|
||||
+ } else if (a === "--offline") {
|
||||
+ opts.offline = true;
|
||||
} else if (a.startsWith("--")) throw new DiscordError(`unknown argument: ${a}\n${USAGE}`, 4);
|
||||
else if (opts.command === null) opts.command = a;
|
||||
else if (opts.binding === null) opts.binding = a;
|
||||
@@ -89,6 +91,7 @@ function parse(argv) {
|
||||
if (!["check", "run", "stop", "unlock", "recover", "reload"].includes(opts.command)) throw new DiscordError(USAGE, 4);
|
||||
if (opts.binding === null) throw new DiscordError(`${opts.command} needs a binding name\n${USAGE}`, 4);
|
||||
if (opts.supervised && opts.command !== "run") throw new DiscordError(`--supervised applies to run only\n${USAGE}`, 4);
|
||||
+ if (opts.offline && opts.command !== "check") throw new DiscordError(`--offline applies to check only\n${USAGE}`, 4);
|
||||
return opts;
|
||||
}
|
||||
|
||||
@@ -123,6 +126,11 @@ async function check(opts) {
|
||||
say(toolRoots ? `tools: ${enabledToolNames(toolRoots).join(",")}; roots ${toolRoots.roots.map((r) => `${r.name}=${r.path}${r.write ? " (writable)" : ""}`).join(" ")}, ${toolRoots.maxCallsPerTurn} calls/message, ${toolRoots.maxFileBytes} bytes/file${toolRoots.web ? `, web via ${toolRoots.web.searxng} (${toolRoots.web.maxFetchBytes} bytes/page)` : ""}${toolRoots.setspark ? `, setspark via ${toolRoots.setspark.baseUrl} as ${toolRoots.setspark.principal}` : ""}` : "tools: none");
|
||||
say(`token file mode 0600 ok; STOP ${stopRequested(journalDir) ? "PRESENT" : "absent"}`);
|
||||
|
||||
+ if (opts.offline) {
|
||||
+ say("offline check passed; no REST or gateway calls were made");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
const rest = createRest({ token, log: warn });
|
||||
const me = await rest.getMe();
|
||||
if (me.id !== binding.botUserId) throw new DiscordError(`token belongs to bot ${me.id}, binding says ${binding.botUserId}`);
|
||||
diff --git a/packages/discord/tests/copy-service.test.mjs b/packages/discord/tests/copy-service.test.mjs
|
||||
new file mode 100644
|
||||
index 000000000..c2445701f
|
||||
--- /dev/null
|
||||
+++ b/packages/discord/tests/copy-service.test.mjs
|
||||
@@ -0,0 +1,26 @@
|
||||
+import test from 'node:test';
|
||||
+import assert from 'node:assert/strict';
|
||||
+import {spawnSync} from 'node:child_process';
|
||||
+import {readFileSync,writeFileSync,mkdirSync,unlinkSync,chmodSync,existsSync} from 'node:fs';
|
||||
+import {join,dirname} from 'node:path';
|
||||
+import {fileURLToPath} from 'node:url';
|
||||
+import {makeRoot} from './helpers.mjs';
|
||||
+const ROOT=fileURLToPath(new URL('../../../',import.meta.url)).replace(/\/$/,'');
|
||||
+const SCRIPT=join(ROOT,'scripts/discord-service.sh');
|
||||
+function setup(){const root=makeRoot(),copies=join(root,'copies'),id='a'.repeat(40),copy=join(copies,id),units=join(root,'units');mkdirSync(join(copy,'packages/discord/src'),{recursive:true});mkdirSync(join(copy,'node_modules/.bin'),{recursive:true});writeFileSync(join(copy,'packages/discord/src/cli.mjs'),'// fixture');writeFileSync(join(copy,'node_modules/.bin/pi'),'#!/bin/sh\nexit 0\n',{mode:0o755});const env={PATH:process.env.PATH,HOME:root,XDG_CONFIG_HOME:join(root,'config')};const run=args=>spawnSync(SCRIPT,args,{env,encoding:'utf8',timeout:5000});return {root,copies,id,copy,units,run,env};}
|
||||
+test('pinned Discord render/install and missing prerequisites; checkout bytes unchanged',()=>{
|
||||
+ const s=setup(),args=['--copy',s.id,'--copies',s.copies],r=s.run(['render',...args]);assert.equal(r.status,0,r.stderr);
|
||||
+ assert.ok(r.stdout.includes(`WorkingDirectory=${s.copy}\n`));assert.ok(r.stdout.includes(`ExecStart=${process.execPath} ${s.copy}/packages/discord/src/cli.mjs run %i --supervised --repo ${s.copy}\n`));
|
||||
+ for(const line of r.stdout.split('\n').filter(l=>/^(ExecStart|WorkingDirectory)=/.test(l)))assert.ok(!line.includes(ROOT),line);
|
||||
+ const legacy=s.run(['render']);assert.equal(legacy.status,0,legacy.stderr);
|
||||
+ const nodeDir=dirname(process.execPath),originalPath='/usr/local/bin:/usr/bin:/bin',path=originalPath.split(':').includes(nodeDir)?originalPath:nodeDir+':'+originalPath;
|
||||
+ const expected=readFileSync(join(ROOT,'packages/discord/systemd/[email protected]'),'utf8').replaceAll('@REPO@',ROOT).replaceAll('@PATH@',path);assert.equal(legacy.stdout,expected);
|
||||
+ assert.equal(r.stdout.split('\n').filter(l=>!/^(ExecStart|WorkingDirectory)=/.test(l)).join('\n'),legacy.stdout.split('\n').filter(l=>!/^(ExecStart|WorkingDirectory)=/.test(l)).join('\n'));
|
||||
+ const installed=s.run(['install',...args,'--dir',s.units,'--no-reload']);assert.equal(installed.status,0,installed.stderr);assert.equal(readFileSync(join(s.units,'[email protected]'),'utf8'),r.stdout);
|
||||
+ for(const id of ['','short','b'.repeat(40)])assert.equal(s.run(['install','--copy',id,'--copies',s.copies,'--dir',join(s.root,'missing'),'--no-reload']).status,1);
|
||||
+ assert.ok(!existsSync(join(s.root,'missing/[email protected]')));
|
||||
+ const cli=join(s.copy,'packages/discord/src/cli.mjs');unlinkSync(cli);assert.equal(s.run(['install',...args,'--dir',join(s.root,'missing-cli'),'--no-reload']).status,1);assert.ok(!existsSync(join(s.root,'missing-cli/[email protected]')));writeFileSync(cli,'// fixture');unlinkSync(join(s.copy,'node_modules/.bin/pi'));assert.equal(s.run(['install',...args,'--dir',join(s.root,'missing-pi'),'--no-reload']).status,1);assert.ok(!existsSync(join(s.root,'missing-pi/[email protected]')));
|
||||
+});
|
||||
+test('Discord status reports effective WorkingDirectory from a fake user manager',()=>{
|
||||
+ const s=setup(),bin=join(s.root,'bin');mkdirSync(bin);const script=join(bin,'systemctl');writeFileSync(script,`#!/bin/sh\ncase "$*" in *show*)echo 'WorkingDirectory=${s.copy}';; *)echo inactive;;esac\n`);chmodSync(script,0o755);s.env.PATH=bin+':'+s.env.PATH;const r=s.run(['status','fixture']);assert.equal(r.status,0,r.stderr);assert.ok(r.stdout.includes(`copy: ${s.id}`));assert.ok(r.stdout.includes(`tree: ${s.copy}`));
|
||||
+});
|
||||
diff --git a/packages/discord/tests/offline-check.test.mjs b/packages/discord/tests/offline-check.test.mjs
|
||||
new file mode 100644
|
||||
index 000000000..ae069322b
|
||||
--- /dev/null
|
||||
+++ b/packages/discord/tests/offline-check.test.mjs
|
||||
@@ -0,0 +1,26 @@
|
||||
+import test from 'node:test';
|
||||
+import assert from 'node:assert/strict';
|
||||
+import {spawnSync} from 'node:child_process';
|
||||
+import {writeFileSync,chmodSync,unlinkSync,existsSync} from 'node:fs';
|
||||
+import {join} from 'node:path';
|
||||
+import {fileURLToPath} from 'node:url';
|
||||
+import {makeRoot,makeRepo,makeDeployment} from './helpers.mjs';
|
||||
+const CLI=fileURLToPath(new URL('../src/cli.mjs',import.meta.url));
|
||||
+function setup(){
|
||||
+ const root=makeRoot(),repo=makeRepo(root),d=makeDeployment(root,{}, {tokenContent:'MTAw.abcdefghijklmnopqrstuvwxyz0123456789'}),calls=join(root,'network-called'),preload=join(root,'no-network.mjs');
|
||||
+ writeFileSync(preload,`import {writeFileSync} from 'node:fs';const refuse=()=>{writeFileSync(${JSON.stringify(calls)},'called');throw Error('fixture refuses network')};globalThis.fetch=refuse;globalThis.WebSocket=class{constructor(){refuse()}};`);
|
||||
+ const run=(command='check',args=[])=>spawnSync(process.execPath,['--import',preload,CLI,command,d.raw.name,'--config',d.config,'--repo',repo,...args],{encoding:'utf8',timeout:5000});
|
||||
+ return {root,repo,d,calls,run};
|
||||
+}
|
||||
+test('check --offline validates local fixture without REST or gateway calls',()=>{
|
||||
+ const s=setup(),r=s.run('check',['--offline']);assert.equal(r.status,0,r.stderr);assert.match(r.stdout,/token file mode 0600 ok; STOP absent/);assert.match(r.stdout,/offline check passed; no REST or gateway calls were made\n$/);assert.ok(!existsSync(s.calls));
|
||||
+ // Online check still reaches REST: the trap fails it immediately.
|
||||
+ const online=s.run();assert.equal(online.status,1);assert.ok(existsSync(s.calls));assert.match(online.stderr,/fixture refuses network/);
|
||||
+});
|
||||
+test('check --offline refuses token mode and missing context under the selected repo',()=>{
|
||||
+ const s=setup();chmodSync(s.d.tokenFile,0o644);const bad=s.run('check',['--offline']);assert.equal(bad.status,2,bad.stderr);assert.match(bad.stderr,/0600/);assert.ok(!existsSync(s.calls));
|
||||
+ chmodSync(s.d.tokenFile,0o600);unlinkSync(join(s.repo,'contracts/CONSTITUTION.md'));const context=s.run('check',['--offline']);assert.equal(context.status,2,context.stderr);assert.ok(!existsSync(s.calls));
|
||||
+});
|
||||
+test('run --offline is a usage refusal before reading config or networking',()=>{
|
||||
+ const s=setup();unlinkSync(s.d.config);const r=s.run('run',['--offline']);assert.equal(r.status,4,r.stderr);assert.match(r.stderr,/--offline/);assert.ok(!existsSync(s.calls));
|
||||
+});
|
||||
diff --git a/scripts/discord-service.sh b/scripts/discord-service.sh
|
||||
index fd2da1de1..ea726e08e 100755
|
||||
--- a/scripts/discord-service.sh
|
||||
+++ b/scripts/discord-service.sh
|
||||
@@ -6,8 +6,8 @@
|
||||
# instance per binding (mosaic-discord@<binding>). Nothing here reads the
|
||||
# binding or the token. See packages/discord/README.md, "Service unit".
|
||||
#
|
||||
-# render print the rendered unit on stdout
|
||||
-# install [--dir DIR] [--no-reload]
|
||||
+# render [--copy ID [--copies DIR]] print the rendered unit on stdout
|
||||
+# install [--copy ID [--copies DIR]] [--dir DIR] [--no-reload]
|
||||
# write the unit (write to a temp file, then
|
||||
# rename) and run `systemctl --user daemon-reload`
|
||||
# uninstall [--dir DIR] [--no-reload]
|
||||
@@ -19,20 +19,44 @@ set -euo pipefail
|
||||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
TEMPLATE="$REPO/packages/discord/systemd/[email protected]"
|
||||
UNIT_NAME="[email protected]"
|
||||
-USAGE="usage: scripts/discord-service.sh render | install [--dir DIR] [--no-reload] | uninstall [--dir DIR] [--no-reload] | status <binding>"
|
||||
+USAGE="usage: scripts/discord-service.sh render [--copy ID [--copies DIR]] | install [--copy ID [--copies DIR]] [--dir DIR] [--no-reload] | uninstall [--dir DIR] [--no-reload] | status <binding>"
|
||||
|
||||
die() { echo "discord-service: $*" >&2; exit 1; }
|
||||
usage() { echo "$USAGE" >&2; exit 4; }
|
||||
|
||||
+COPY_ID=""
|
||||
+COPY_SET=0
|
||||
+COPIES="${XDG_DATA_HOME:-$HOME/.local/share}/mosaic-stack/copies"
|
||||
+copy_root() {
|
||||
+ [[ "$COPY_ID" =~ ^[0-9a-f]{40}$ ]] || die '--copy must be a full 40-character commit id'
|
||||
+ case "$COPIES" in /*) ;; *) die 'copies directory must be absolute' ;; esac
|
||||
+ case "$COPIES" in *[!A-Za-z0-9/._+-]*) die 'copies directory contains a character the unit cannot carry' ;; esac
|
||||
+ local root="$COPIES/$COPY_ID"
|
||||
+ [ -f "$root/packages/discord/src/cli.mjs" ] || die "copy lacks packages/discord/src/cli.mjs: $root"
|
||||
+ [ -f "$root/node_modules/.bin/pi" ] || die "copy lacks node_modules/.bin/pi: $root"
|
||||
+ echo "$root"
|
||||
+}
|
||||
+
|
||||
render() {
|
||||
[ -f "$TEMPLATE" ] || die "template missing: $TEMPLATE"
|
||||
+ local root=""
|
||||
+ [ "$COPY_SET" = 0 ] || root="$(copy_root)" || return 1
|
||||
local node_dir
|
||||
node_dir="$(dirname "$(command -v node || true)")"
|
||||
[ -n "$node_dir" ] && [ "$node_dir" != "." ] || die "node not found on PATH"
|
||||
local path="/usr/local/bin:/usr/bin:/bin"
|
||||
case ":$path:" in *":$node_dir:"*) ;; *) path="$node_dir:$path" ;; esac
|
||||
case "$REPO" in *@*|*'|'*) die "repository path contains a character the template cannot carry: $REPO" ;; esac
|
||||
- sed -e "s|@REPO@|$REPO|g" -e "s|@PATH@|$path|g" "$TEMPLATE"
|
||||
+ local unit
|
||||
+ unit="$(sed -e "s|@REPO@|$REPO|g" -e "s|@PATH@|$path|g" "$TEMPLATE")" || return 1
|
||||
+ if [ -n "$root" ]; then
|
||||
+ local node
|
||||
+ node="$(command -v node)"
|
||||
+ case "$node" in *[!A-Za-z0-9/._+-]*) die 'node path contains a character the unit cannot carry' ;; esac
|
||||
+ sed -e "s|^WorkingDirectory=.*$|WorkingDirectory=$root|" -e "s|^ExecStart=.*$|ExecStart=$node $root/packages/discord/src/cli.mjs run %i --supervised --repo $root|" <<< "$unit"
|
||||
+ else
|
||||
+ printf '%s\n' "$unit"
|
||||
+ fi
|
||||
}
|
||||
|
||||
# Parse --dir and --no-reload for install and uninstall.
|
||||
@@ -41,8 +65,10 @@ RELOAD=1
|
||||
parse_flags() {
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
- --dir) [ $# -ge 2 ] || usage; UNIT_DIR="$2"; shift 2 ;;
|
||||
- --no-reload) RELOAD=0; shift ;;
|
||||
+ --copy) [ "$cmd" != uninstall ] && [ $# -ge 2 ] || usage; COPY_ID="$2"; COPY_SET=1; shift 2 ;;
|
||||
+ --copies) [ "$cmd" != uninstall ] && [ $# -ge 2 ] || usage; COPIES="$2"; shift 2 ;;
|
||||
+ --dir) [ "$cmd" != render ] && [ $# -ge 2 ] || usage; UNIT_DIR="$2"; shift 2 ;;
|
||||
+ --no-reload) [ "$cmd" != render ] || usage; RELOAD=0; shift ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
@@ -50,10 +76,12 @@ parse_flags() {
|
||||
|
||||
install_unit() {
|
||||
parse_flags "$@"
|
||||
+ local rendered
|
||||
+ rendered="$(render)" || exit 1
|
||||
mkdir -p "$UNIT_DIR"
|
||||
local target="$UNIT_DIR/$UNIT_NAME" tmp
|
||||
tmp="$(mktemp "$UNIT_DIR/.$UNIT_NAME.XXXXXX")"
|
||||
- render > "$tmp"
|
||||
+ printf '%s\n' "$rendered" > "$tmp"
|
||||
chmod 0644 "$tmp"
|
||||
if [ -f "$target" ] && cmp -s "$tmp" "$target"; then
|
||||
rm -f "$tmp"
|
||||
@@ -66,6 +94,11 @@ install_unit() {
|
||||
systemctl --user daemon-reload || die "daemon-reload failed"
|
||||
echo "daemon-reload done"
|
||||
fi
|
||||
+ if [ -n "$COPY_ID" ]; then
|
||||
+ echo "copy: $COPY_ID"
|
||||
+ else
|
||||
+ echo "checkout: $REPO (use --copy ID to pin the connector)"
|
||||
+ fi
|
||||
cat <<MSG
|
||||
next, for one binding:
|
||||
systemctl --user enable --now mosaic-discord@<binding> start now and at login
|
||||
@@ -106,6 +139,17 @@ status_unit() {
|
||||
echo " enabled: $(systemctl --user is-enabled "$unit" 2>&1 || true)"
|
||||
echo " active: $(systemctl --user is-active "$unit" 2>&1 || true)"
|
||||
systemctl --user show "$unit" -p MainPID -p NRestarts -p ExecMainStartTimestamp -p Result 2>/dev/null | sed 's/^/ /'
|
||||
+ local working id
|
||||
+ working="$(systemctl --user show "$unit" -p WorkingDirectory --value 2>/dev/null)" || working=""
|
||||
+ # Accept property-form output too, for systemctl fixtures and older wrappers.
|
||||
+ working="${working#WorkingDirectory=}"
|
||||
+ echo " tree: ${working:-absent (WorkingDirectory unavailable)}"
|
||||
+ id="${working##*/}"
|
||||
+ if [[ "$id" =~ ^[0-9a-f]{40}$ ]] && [ -f "$working/packages/discord/src/cli.mjs" ]; then
|
||||
+ echo " copy: $id"
|
||||
+ elif [ -n "$working" ]; then
|
||||
+ echo " checkout: $working"
|
||||
+ fi
|
||||
local config="${XDG_CONFIG_HOME:-$HOME/.config}/mosaic-dev/config.json"
|
||||
local data_root
|
||||
data_root="$(node -e 'const c=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")); process.stdout.write(String(c.dataRoot||""))' "$config" 2>/dev/null || true)"
|
||||
@@ -130,7 +174,7 @@ status_unit() {
|
||||
[ $# -ge 1 ] || usage
|
||||
cmd="$1"; shift
|
||||
case "$cmd" in
|
||||
- render) [ $# -eq 0 ] || usage; render ;;
|
||||
+ render) parse_flags "$@"; render ;;
|
||||
install) install_unit "$@" ;;
|
||||
uninstall) uninstall_unit "$@" ;;
|
||||
status) status_unit "$@" ;;
|
||||
diff --git a/scripts/test-discord.sh b/scripts/test-discord.sh
|
||||
index 9ec4ddda5..109dfdfa5 100755
|
||||
--- a/scripts/test-discord.sh
|
||||
+++ b/scripts/test-discord.sh
|
||||
@@ -209,6 +209,62 @@ env -u USER scripts/discord-service.sh install --dir "$SANDBOX/units-nouser" --n
|
||||
&& grep -qF "enable-linger $(id -un)" "$SANDBOX/install.nouser"
|
||||
check "service install with USER unset finishes and names the account for lingering" $?
|
||||
|
||||
+# --- pinned copy service, fixture paths only ---
|
||||
+COPY_ID="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
+COPIES="$SANDBOX/copies"
|
||||
+COPY="$COPIES/$COPY_ID"
|
||||
+mkdir -p "$COPY/packages/discord/src" "$COPY/node_modules/.bin"
|
||||
+printf '// fixture\n' > "$COPY/packages/discord/src/cli.mjs"
|
||||
+printf '#!/bin/sh\nexit 0\n' > "$COPY/node_modules/.bin/pi"
|
||||
+chmod 755 "$COPY/node_modules/.bin/pi"
|
||||
+scripts/discord-service.sh render --copy "$COPY_ID" --copies "$COPIES" >"$SANDBOX/unit.copy" 2>&1 \
|
||||
+ && grep -qxF "WorkingDirectory=$COPY" "$SANDBOX/unit.copy" \
|
||||
+ && grep -qxF "ExecStart=$(command -v node) $COPY/packages/discord/src/cli.mjs run %i --supervised --repo $COPY" "$SANDBOX/unit.copy" \
|
||||
+ && ! sed -n '/^WorkingDirectory=/p;/^ExecStart=/p' "$SANDBOX/unit.copy" | grep -qF "$REPO_DIR"
|
||||
+check "pinned render runs copy CLI with copy cwd/repo and no checkout executable" $?
|
||||
+scripts/discord-service.sh install --copy "$COPY_ID" --copies "$COPIES" --dir "$SANDBOX/pinned-units" --no-reload >"$SANDBOX/pinned-install" 2>&1 \
|
||||
+ && cmp -s "$SANDBOX/pinned-units/[email protected]" "$SANDBOX/unit.copy"
|
||||
+check "pinned install writes the rendered unit in a temporary unit directory" $?
|
||||
+for kind in short missing cli pi; do
|
||||
+ id="$COPY_ID"
|
||||
+ case "$kind" in short) id=short;; missing) id=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb;; cli) rm "$COPY/packages/discord/src/cli.mjs";; pi) printf '// fixture\n' > "$COPY/packages/discord/src/cli.mjs";rm "$COPY/node_modules/.bin/pi";; esac
|
||||
+ scripts/discord-service.sh install --copy "$id" --copies "$COPIES" --dir "$SANDBOX/refused-$kind" --no-reload >"$SANDBOX/refused-$kind.log" 2>&1
|
||||
+ rc=$?
|
||||
+ [ "$rc" -eq 1 ] && [ ! -e "$SANDBOX/refused-$kind/[email protected]" ]
|
||||
+ check "pinned install refuses $kind prerequisite and writes no unit" $?
|
||||
+done
|
||||
+printf '#!/bin/sh\nexit 0\n' > "$COPY/node_modules/.bin/pi"
|
||||
+mkdir -p "$SANDBOX/fake-bin"
|
||||
+cat >"$SANDBOX/fake-bin/systemctl" <<EOF_STATUS
|
||||
+#!/bin/sh
|
||||
+case "\$*" in *show*) echo 'WorkingDirectory=$COPY';; *)echo inactive;; esac
|
||||
+EOF_STATUS
|
||||
+chmod 755 "$SANDBOX/fake-bin/systemctl"
|
||||
+HOME="$SANDBOX/home" XDG_CONFIG_HOME="$SANDBOX/config-home" PATH="$SANDBOX/fake-bin:$PATH" scripts/discord-service.sh status fixture >"$SANDBOX/status.copy" 2>&1 \
|
||||
+ && grep -qF "copy: $COPY_ID" "$SANDBOX/status.copy" \
|
||||
+ && grep -qF "tree: $COPY" "$SANDBOX/status.copy"
|
||||
+check "status names the copy from fake systemctl effective cwd" $?
|
||||
+grep -qF "checkout: $REPO_DIR" "$SANDBOX/install.1" && grep -qF -- '--copy' "$SANDBOX/install.1"
|
||||
+check "checkout install explicitly recommends --copy" $?
|
||||
+
|
||||
+# Offline wrapper entrypoint. Only a synthetic token/binding/config are used.
|
||||
+node - "$SANDBOX" <<'EOF_OFFLINE'
|
||||
+const fs=require('fs'),p=require('path'),root=process.argv[2];
|
||||
+const data=p.join(root,'offline-data');fs.mkdirSync(p.join(data,'discord'),{recursive:true});
|
||||
+const b=JSON.parse(fs.readFileSync('packages/discord/fixtures/binding.example.json','utf8'));
|
||||
+b.name='offline-fixture';b.tokenFile=p.join(root,'offline-token');b.context.files=['packages/discord/README.md'];delete b.tools;
|
||||
+fs.writeFileSync(b.tokenFile,'MTAw.abcdefghijklmnopqrstuvwxyz0123456789\n',{mode:0o600});
|
||||
+fs.writeFileSync(p.join(data,'discord',b.name+'.json'),JSON.stringify(b),{mode:0o600});
|
||||
+fs.writeFileSync(p.join(root,'offline-config'),JSON.stringify({dataRoot:data}));
|
||||
+fs.writeFileSync(p.join(root,'offline-trap.mjs'),`globalThis.fetch=()=>{throw Error('network forbidden in offline fixture')};globalThis.WebSocket=class{constructor(){throw Error('gateway forbidden in offline fixture')}};`);
|
||||
+EOF_OFFLINE
|
||||
+NODE_OPTIONS="--import=$SANDBOX/offline-trap.mjs" scripts/discord.sh check offline-fixture --config "$SANDBOX/offline-config" --offline >"$SANDBOX/offline.out" 2>&1 \
|
||||
+ && grep -qF 'offline check passed; no REST or gateway calls were made' "$SANDBOX/offline.out"
|
||||
+check "wrapper check --offline passes local fixture and makes no network call" $?
|
||||
+NODE_OPTIONS="--import=$SANDBOX/offline-trap.mjs" scripts/discord.sh run offline-fixture --config "$SANDBOX/offline-config" --offline >"$SANDBOX/offline-run.out" 2>&1
|
||||
+[ $? -eq 4 ]
|
||||
+check "wrapper run --offline refuses usage" $?
|
||||
+
|
||||
echo
|
||||
echo "discord suite: $PASS passed, $FAIL failed"
|
||||
[ "$FAIL" -eq 0 ]
|
||||
@@ -0,0 +1,7 @@
|
||||
1cd5abf22a24b985e262a7d0baed7c5bf66e7a3cbafb47c06dd51eace4afe9f6 docs/TOOLS.md
|
||||
5949914eb943139d18cac183755319e96830ea92be3b33f3bd056536467655d2 packages/discord/README.md
|
||||
8f18ee5bb7e0c2a049670caf20438cc35c44bcd5333943e9084f0abb6c9e2234 packages/discord/src/cli.mjs
|
||||
75a84d470cc5cb77f0a9252f8828dfbe29645958f423884846a0ac39d9e51ced packages/discord/tests/copy-service.test.mjs
|
||||
c56f4f93e66bc979f0e95e52c2ab2a09b149191071de09b8d739b0a0592df8fe packages/discord/tests/offline-check.test.mjs
|
||||
bc290fcf852930f398b44689f0731d00412c475781b3eae51c0a01beaf5d1102 scripts/discord-service.sh
|
||||
f5b42da59265a3195fa5c87053dbc3dcdcec9ecc9d5468cbadeb683346e62e34 scripts/test-discord.sh
|
||||
@@ -0,0 +1,7 @@
|
||||
docs/TOOLS.md
|
||||
packages/discord/README.md
|
||||
packages/discord/src/cli.mjs
|
||||
packages/discord/tests/copy-service.test.mjs
|
||||
packages/discord/tests/offline-check.test.mjs
|
||||
scripts/discord-service.sh
|
||||
scripts/test-discord.sh
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run shell suites from a detached worktree, capture every exit."""
|
||||
import pathlib, subprocess, json, datetime, sys, os
|
||||
root=pathlib.Path.cwd()
|
||||
if root == pathlib.Path('/mnt/storage/src/mosaic-stack'):
|
||||
raise SystemExit('gate refuses canonical checkout')
|
||||
if subprocess.check_output(['git','branch','--show-current'],text=True).strip():
|
||||
raise SystemExit('gate requires detached HEAD')
|
||||
if os.environ.get('DOCKER_HOST') != 'unix:///nonexistent.sock':
|
||||
raise SystemExit('decision 127 requires DOCKER_HOST=unix:///nonexistent.sock')
|
||||
out=pathlib.Path(sys.argv[1]); out.mkdir(parents=True,exist_ok=True)
|
||||
results=[]
|
||||
commands=[]
|
||||
for package in [root/'packages/discord']:
|
||||
tests=sorted(package.glob('tests/**/*.test.mjs'))
|
||||
if tests: commands.append(('node-'+package.name,['node','--test',*[str(t.relative_to(root)) for t in tests]],600))
|
||||
for suite in sorted(root.glob('scripts/test-*.sh')):
|
||||
commands.append((suite.stem,['bash',str(suite)],600))
|
||||
for name,command,timeout in commands:
|
||||
began=datetime.datetime.now(datetime.timezone.utc).isoformat()
|
||||
with (out/(name+'.txt')).open('w') as log:
|
||||
p=subprocess.run(command,cwd=root,stdout=log,stderr=subprocess.STDOUT,timeout=timeout)
|
||||
row={'suite':name,'exit':p.returncode,'began':began,'ended':datetime.datetime.now(datetime.timezone.utc).isoformat()}
|
||||
results.append(row)
|
||||
(out/'gate-results.json').write_text(json.dumps(results,indent=2)+'\n')
|
||||
print(json.dumps(row),flush=True)
|
||||
sys.exit(any(r['exit'] for r in results))
|
||||
@@ -0,0 +1,22 @@
|
||||
import pathlib,subprocess,sys,json,os,signal
|
||||
root=pathlib.Path.cwd();out=pathlib.Path(sys.argv[1]);out.mkdir(parents=True,exist_ok=True)
|
||||
assert root != pathlib.Path('/mnt/storage/src/mosaic-stack')
|
||||
assert os.environ.get('DOCKER_HOST')=='unix:///nonexistent.sock'
|
||||
muts=[
|
||||
('checkout-execstart','scripts/discord-service.sh','ExecStart=$node $root/packages/discord/src/cli.mjs run %i --supervised --repo $root','ExecStart=$REPO/scripts/discord.sh run %i --supervised','pinned Discord'),
|
||||
('missing-pi-accepted','scripts/discord-service.sh','[ -f "$root/node_modules/.bin/pi" ] || die',': || die','pinned Discord'),
|
||||
('offline-calls-getMe','packages/discord/src/cli.mjs','if (opts.offline) {','if (false) {','check --offline validates'),
|
||||
]
|
||||
results=[]
|
||||
for name,file,old,new,pattern in muts:
|
||||
p=root/file;original=p.read_text();assert original.count(old)==1,(name,original.count(old))
|
||||
try:
|
||||
p.write_text(original.replace(old,new,1))
|
||||
with (out/(name+'.txt')).open('w') as log:
|
||||
child=subprocess.Popen(['node','--test','--test-name-pattern='+pattern,'packages/discord/tests/copy-service.test.mjs' if name!='offline-calls-getMe' else 'packages/discord/tests/offline-check.test.mjs'],stdout=log,stderr=subprocess.STDOUT,start_new_session=True)
|
||||
try:code=child.wait(timeout=60);status='killed' if code else 'survived'
|
||||
except subprocess.TimeoutExpired:os.killpg(child.pid,signal.SIGKILL);child.wait();code=124;status='timeout'
|
||||
row={'name':name,'exit':code,'status':status};results.append(row);print(json.dumps(row),flush=True)
|
||||
(out/'summary.json').write_text(json.dumps(results,indent=2)+'\n')
|
||||
finally:p.write_text(original)
|
||||
sys.exit(any(r['status']!='killed' for r in results))
|
||||
@@ -0,0 +1,39 @@
|
||||
# Mosaic Discord connector, one instance per binding: mosaic-discord@<binding>.
|
||||
# Rendered by scripts/discord-service.sh from packages/discord/systemd/;
|
||||
# /mnt/storage/src/mosaic-stack-rocko-114 and /usr/local/bin:/usr/bin:/bin are filled in at install time. Edit the template and
|
||||
# reinstall; do not edit the installed copy.
|
||||
[Unit]
|
||||
Description=Mosaic Discord connector (%i)
|
||||
Documentation=file:///mnt/storage/src/mosaic-stack-rocko-114/packages/discord/README.md
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
StartLimitIntervalSec=600
|
||||
StartLimitBurst=5
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/mnt/storage/src/mosaic-stack-rocko-114
|
||||
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
||||
# A supervised run first clears a lock left by a crash or a reboot, and
|
||||
# refuses with exit 3 while STOP is present or the binding is held. Exit 3
|
||||
# is never retried: the brake belongs to the operator. This happens inside
|
||||
# the main process on purpose: systemd honours RestartPreventExitStatus only
|
||||
# for the main process, so a refusing ExecStartPre would loop.
|
||||
ExecStart=/mnt/storage/src/mosaic-stack-rocko-114/scripts/discord.sh run %i --supervised
|
||||
# `systemctl --user reload` re-reads the binding in place (channels, users,
|
||||
# limits); the connector refuses and keeps the old binding when the file is
|
||||
# invalid or a fixed key changed. See reloads.jsonl under the journal.
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=on-failure
|
||||
RestartSec=15
|
||||
RestartPreventExitStatus=3
|
||||
# `systemctl --user stop` sends SIGTERM; the connector finishes the turn in
|
||||
# flight, then exits 0. The stop timeout covers the largest turn timeout a
|
||||
# binding may set (3600 s) plus margin; a normal stop takes seconds.
|
||||
KillSignal=SIGTERM
|
||||
KillMode=mixed
|
||||
TimeoutStopSec=3700
|
||||
NoNewPrivileges=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -0,0 +1,39 @@
|
||||
# Mosaic Discord connector, one instance per binding: mosaic-discord@<binding>.
|
||||
# Rendered by scripts/discord-service.sh from packages/discord/systemd/;
|
||||
# /mnt/storage/src/mosaic-stack-rocko-114 and /usr/local/bin:/usr/bin:/bin are filled in at install time. Edit the template and
|
||||
# reinstall; do not edit the installed copy.
|
||||
[Unit]
|
||||
Description=Mosaic Discord connector (%i)
|
||||
Documentation=file:///mnt/storage/src/mosaic-stack-rocko-114/packages/discord/README.md
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
StartLimitIntervalSec=600
|
||||
StartLimitBurst=5
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/mnt/storage/src/mosaic-stack-rocko-114
|
||||
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
||||
# A supervised run first clears a lock left by a crash or a reboot, and
|
||||
# refuses with exit 3 while STOP is present or the binding is held. Exit 3
|
||||
# is never retried: the brake belongs to the operator. This happens inside
|
||||
# the main process on purpose: systemd honours RestartPreventExitStatus only
|
||||
# for the main process, so a refusing ExecStartPre would loop.
|
||||
ExecStart=/mnt/storage/src/mosaic-stack-rocko-114/scripts/discord.sh run %i --supervised
|
||||
# `systemctl --user reload` re-reads the binding in place (channels, users,
|
||||
# limits); the connector refuses and keeps the old binding when the file is
|
||||
# invalid or a fixed key changed. See reloads.jsonl under the journal.
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=on-failure
|
||||
RestartSec=15
|
||||
RestartPreventExitStatus=3
|
||||
# `systemctl --user stop` sends SIGTERM; the connector finishes the turn in
|
||||
# flight, then exits 0. The stop timeout covers the largest turn timeout a
|
||||
# binding may set (3600 s) plus margin; a normal stop takes seconds.
|
||||
KillSignal=SIGTERM
|
||||
KillMode=mixed
|
||||
TimeoutStopSec=3700
|
||||
NoNewPrivileges=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -0,0 +1 @@
|
||||
7/7 hashes match; patch equals final detached diff; detached base matches; checkout before/after renders match; canonical patch applies and diff clean; own logs-only commit 618d71ebbb48d2a36512f84ca1c29624289a4423; protected live actions unexecuted.
|
||||
@@ -0,0 +1,68 @@
|
||||
[
|
||||
{
|
||||
"suite": "node-discord",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:30:20.823230+00:00",
|
||||
"ended": "2026-10-14T20:30:23.493776+00:00"
|
||||
},
|
||||
{
|
||||
"suite": "test-auth",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:30:23.494015+00:00",
|
||||
"ended": "2026-10-14T20:30:24.109265+00:00"
|
||||
},
|
||||
{
|
||||
"suite": "test-conductor",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:30:24.109530+00:00",
|
||||
"ended": "2026-10-14T20:30:30.287430+00:00"
|
||||
},
|
||||
{
|
||||
"suite": "test-config",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:30:30.287694+00:00",
|
||||
"ended": "2026-10-14T20:30:31.955466+00:00"
|
||||
},
|
||||
{
|
||||
"suite": "test-discord",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:30:31.955716+00:00",
|
||||
"ended": "2026-10-14T20:30:38.736178+00:00"
|
||||
},
|
||||
{
|
||||
"suite": "test-extension-package",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:30:38.736435+00:00",
|
||||
"ended": "2026-10-14T20:30:40.052900+00:00"
|
||||
},
|
||||
{
|
||||
"suite": "test-foundation",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:30:40.053145+00:00",
|
||||
"ended": "2026-10-14T20:31:17.005136+00:00"
|
||||
},
|
||||
{
|
||||
"suite": "test-land-work",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:31:17.005438+00:00",
|
||||
"ended": "2026-10-14T20:31:25.637399+00:00"
|
||||
},
|
||||
{
|
||||
"suite": "test-queue",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:31:25.637691+00:00",
|
||||
"ended": "2026-10-14T20:31:45.992880+00:00"
|
||||
},
|
||||
{
|
||||
"suite": "test-release",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:31:45.993134+00:00",
|
||||
"ended": "2026-10-14T20:31:46.157295+00:00"
|
||||
},
|
||||
{
|
||||
"suite": "test-task",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:31:46.157584+00:00",
|
||||
"ended": "2026-10-14T20:31:48.126073+00:00"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,191 @@
|
||||
✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (7.7375ms)
|
||||
✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.728755ms)
|
||||
✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.660941ms)
|
||||
✔ approvals: a button approves only on its own request message with the matching custom id (0.480185ms)
|
||||
✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (17.296632ms)
|
||||
✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (6.588519ms)
|
||||
✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.386651ms)
|
||||
✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.280338ms)
|
||||
✔ authorize: open channel, listed user (1.82861ms)
|
||||
✔ authorize: wrong guild (0.184147ms)
|
||||
✔ authorize: no guild (DM) (0.172822ms)
|
||||
✔ authorize: unlisted channel (0.174402ms)
|
||||
✔ authorize: unknown channel, no info (0.177221ms)
|
||||
✔ authorize: thread of listed parent (0.21491ms)
|
||||
✔ authorize: thread of unlisted parent (0.154106ms)
|
||||
✔ authorize: text channel that is not a thread and not listed (0.137261ms)
|
||||
✔ authorize: unlisted user (0.987234ms)
|
||||
✔ authorize: no author (0.258428ms)
|
||||
✔ authorize: bot author (listed id, bot flag) (0.130292ms)
|
||||
✔ authorize: system author (0.116261ms)
|
||||
✔ authorize: the bot itself (0.09737ms)
|
||||
✔ authorize: webhook (0.098117ms)
|
||||
✔ authorize: mention channel without mention (0.168159ms)
|
||||
✔ authorize: mention channel with bot mention (0.157334ms)
|
||||
✔ authorize: mention channel with @everyone only (0.126031ms)
|
||||
✔ authorize: mention channel mentioning someone else (0.079199ms)
|
||||
✔ authorize: mention channel, content says @bot but mentions empty (0.211148ms)
|
||||
✔ authorize: private thread under mention channel, mentioned (0.096633ms)
|
||||
✔ authorize: private thread under mention channel, not mentioned (0.075681ms)
|
||||
✔ authorize: thread in another guild per channel info (0.096333ms)
|
||||
✔ authorize: not an object (0.069499ms)
|
||||
✔ authorize: no id (0.101974ms)
|
||||
✔ authorize: oversize content is accepted and flagged (0.072529ms)
|
||||
✔ authorize: exactly the limit is not oversize (0.062169ms)
|
||||
✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.572655ms)
|
||||
✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.220375ms)
|
||||
✔ binding: a complete binding validates and is frozen (2.592724ms)
|
||||
✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.277499ms)
|
||||
✔ binding: empty allowlists refuse (0.484324ms)
|
||||
✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.35884ms)
|
||||
✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.544901ms)
|
||||
✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (2.632685ms)
|
||||
✔ binding: file must be 0600, regular, not a symlink (1.696578ms)
|
||||
✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.650015ms)
|
||||
✔ cli: check refuses a non-0600 token file with exit 2 before any network use (110.716888ms)
|
||||
✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.727265ms)
|
||||
✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (311.22442ms)
|
||||
✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (188.64441ms)
|
||||
✔ cli: run refuses when STOP is present, before any network use (127.115842ms)
|
||||
✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (2.407766ms)
|
||||
✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.414096ms)
|
||||
✔ 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 (20.411015ms)
|
||||
✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (16.46198ms)
|
||||
✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.651165ms)
|
||||
✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.257068ms)
|
||||
✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.394563ms)
|
||||
✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.649376ms)
|
||||
✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.223894ms)
|
||||
✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.349565ms)
|
||||
✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.081032ms)
|
||||
✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.035414ms)
|
||||
✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.067365ms)
|
||||
✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.137564ms)
|
||||
✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.021324ms)
|
||||
✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.161459ms)
|
||||
✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.513163ms)
|
||||
✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (4.309453ms)
|
||||
✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.219847ms)
|
||||
✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.495667ms)
|
||||
✔ 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 (2.66673ms)
|
||||
✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.170439ms)
|
||||
✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (3.946203ms)
|
||||
✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (1.991666ms)
|
||||
✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (2.529012ms)
|
||||
✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.626272ms)
|
||||
✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.755232ms)
|
||||
✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.828371ms)
|
||||
✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.09758ms)
|
||||
✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (2.720755ms)
|
||||
✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.18ms)
|
||||
✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.640898ms)
|
||||
✔ pinned Discord render/install and missing prerequisites; checkout bytes unchanged (90.714262ms)
|
||||
✔ Discord status reports effective WorkingDirectory from a fake user manager (49.6019ms)
|
||||
✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (3.009851ms)
|
||||
✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.442298ms)
|
||||
✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (61.214272ms)
|
||||
✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (41.922629ms)
|
||||
✔ engine: one prompt, one turn, text and usage come back (42.573884ms)
|
||||
✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (343.382019ms)
|
||||
✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (230.35888ms)
|
||||
✔ engine: timeout sends abort and fails only that turn; the process stays (103.261162ms)
|
||||
✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (231.288342ms)
|
||||
✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (126.827973ms)
|
||||
✔ 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.661556ms)
|
||||
✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (615.487598ms)
|
||||
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.370293ms)
|
||||
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.537854ms)
|
||||
✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (427.933175ms)
|
||||
✔ engine: a malformed JSONL line fails the turn, not the process (22.736136ms)
|
||||
✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.413276ms)
|
||||
✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.711402ms)
|
||||
✔ gateway: missed ack closes the socket and resumes with the last sequence (1.939565ms)
|
||||
✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.591266ms)
|
||||
✔ gateway: op 9 resumable resumes (0.3611ms)
|
||||
✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.954906ms)
|
||||
✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.572683ms)
|
||||
✔ gateway: close() is final and unparseable frames are ignored (0.49881ms)
|
||||
✔ git: config validation is strict, needs write: true, a work tree and a private token file (68.576099ms)
|
||||
✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (45.56167ms)
|
||||
✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (73.765148ms)
|
||||
✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.28583ms)
|
||||
✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (73.301209ms)
|
||||
✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (87.53822ms)
|
||||
✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (82.429664ms)
|
||||
✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (187.748855ms)
|
||||
✔ git: push pushes the named branch only and reports up to date (68.54912ms)
|
||||
✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (77.62595ms)
|
||||
✔ git: the credential helper answers get over https from a private file and nothing else (192.45159ms)
|
||||
✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (350.996448ms)
|
||||
✔ lock: the claim is exclusive; a second start against a live owner refuses (4.442589ms)
|
||||
✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (80.14209ms)
|
||||
✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (0.879317ms)
|
||||
✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (1.878001ms)
|
||||
✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (51.730431ms)
|
||||
✔ 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 (305.241917ms)
|
||||
✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.436451ms)
|
||||
✔ lock: a process whose start marker or boot id cannot be read refuses to claim (22.13892ms)
|
||||
✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.941645ms)
|
||||
✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (40.69254ms)
|
||||
✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (131.741059ms)
|
||||
✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (80.649113ms)
|
||||
✔ notices: a kind is recorded per UTC day and found again (0.40637ms)
|
||||
✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.113329ms)
|
||||
✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.061449ms)
|
||||
✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.419553ms)
|
||||
✔ check --offline validates local fixture without REST or gateway calls (164.936804ms)
|
||||
✔ check --offline refuses token mode and missing context under the selected repo (115.255403ms)
|
||||
✔ run --offline is a usage refusal before reading config or networking (64.658492ms)
|
||||
✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (50.07082ms)
|
||||
✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (36.622472ms)
|
||||
✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (31.735013ms)
|
||||
✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (68.023788ms)
|
||||
✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (615.329128ms)
|
||||
✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.266712ms)
|
||||
✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.491416ms)
|
||||
✔ rest: content and nonce limits are enforced locally; typing never throws (0.631621ms)
|
||||
✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.842643ms)
|
||||
✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.817126ms)
|
||||
✔ setspark config: a bare https or loopback origin, a private key file, a principal (4.827514ms)
|
||||
✔ setspark config: reaches the tools config and the binding as a fixed key (2.052688ms)
|
||||
✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.176934ms)
|
||||
✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (1.479781ms)
|
||||
✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (23.175566ms)
|
||||
✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (9.630839ms)
|
||||
✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.136501ms)
|
||||
✔ setspark keys: read per call, one printable token per file, rotation without a restart (3.791747ms)
|
||||
✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.668929ms)
|
||||
✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.01183ms)
|
||||
✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1008.960502ms)
|
||||
✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.476089ms)
|
||||
✔ 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 (6.187784ms)
|
||||
✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.233657ms)
|
||||
✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.199212ms)
|
||||
✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.273972ms)
|
||||
✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (3.935152ms)
|
||||
✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.904793ms)
|
||||
✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.228771ms)
|
||||
✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.559234ms)
|
||||
✔ tools: listing and search caps hold (7.52236ms)
|
||||
✔ tools: credential shapes are caught; ordinary prose and ids are not (0.58763ms)
|
||||
✔ 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 (3.978095ms)
|
||||
✔ tools: an unreadable file under the root is skipped by search and refused by read (0.943191ms)
|
||||
✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (0.875055ms)
|
||||
✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (4.810605ms)
|
||||
✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.382867ms)
|
||||
✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.31175ms)
|
||||
✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.186139ms)
|
||||
✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (1.989368ms)
|
||||
✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1022.146076ms)
|
||||
✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (4.200609ms)
|
||||
✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.223248ms)
|
||||
✔ 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 (2.564412ms)
|
||||
✔ 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.333835ms)
|
||||
ℹ tests 183
|
||||
ℹ suites 0
|
||||
ℹ pass 183
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 2631.617248
|
||||
@@ -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,23 @@
|
||||
On branch refactor
|
||||
Your branch is up to date with 'origin/refactor'.
|
||||
|
||||
nothing to commit, working tree clean
|
||||
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,82 @@
|
||||
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/notify.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/copy-service.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/notify.test.mjs
|
||||
OK syntax: packages/discord/tests/offline-check.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 183)
|
||||
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
|
||||
OK pinned render runs copy CLI with copy cwd/repo and no checkout executable
|
||||
OK pinned install writes the rendered unit in a temporary unit directory
|
||||
OK pinned install refuses short prerequisite and writes no unit
|
||||
OK pinned install refuses missing prerequisite and writes no unit
|
||||
OK pinned install refuses cli prerequisite and writes no unit
|
||||
OK pinned install refuses pi prerequisite and writes no unit
|
||||
OK status names the copy from fake systemctl effective cwd
|
||||
OK checkout install explicitly recommends --copy
|
||||
OK wrapper check --offline passes local fixture and makes no network call
|
||||
OK wrapper run --offline refuses usage
|
||||
|
||||
discord suite: 78 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
|
||||
/mnt/storage/src/mosaic-stack-rocko-114/scripts/test-extension-package.sh: line 14: 1903040 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,86 @@
|
||||
OK args: exit 2, target-branch
|
||||
OK args: exit 2, target-branch
|
||||
OK args: exit 4
|
||||
OK args: exit 4
|
||||
OK args: exit 4
|
||||
OK --base next refuses before the config is read (got 2)
|
||||
OK a missing system config exits 3 (got 3)
|
||||
OK nobus: exit 3
|
||||
OK schema-table: exit 2, bus-schema
|
||||
OK schema-meta: exit 2, bus-schema
|
||||
OK openBus opens the database read-only
|
||||
OK other-business: exit 2, decision-not-found
|
||||
OK no-raise: exit 2, decision-not-found
|
||||
OK not-a-merge: exit 2, not-a-merge
|
||||
OK open: exit 2, not-resolved
|
||||
OK withdrawn: exit 2, not-resolved
|
||||
OK by-agent: exit 2, not-human
|
||||
OK via-webui: exit 2, not-human
|
||||
OK no-answer: exit 2, not-human
|
||||
OK answer-role: exit 2, not-human
|
||||
OK answer-run: exit 2, not-human
|
||||
OK answer-choice: exit 2, not-human
|
||||
OK two-answers: exit 2, not-human
|
||||
OK chose-no: exit 2, not-merge-option
|
||||
OK two-refs: exit 2, work-ref
|
||||
OK bad-ref: exit 2, work-ref
|
||||
OK task-ref-disagrees: exit 2, work-ref
|
||||
OK raise-task-disagrees: exit 2, work-ref
|
||||
OK target-main: exit 2, target-branch
|
||||
OK superseded-question: exit 2, superseded
|
||||
OK superseded-task: exit 2, superseded
|
||||
OK superseded-by: exit 2, superseded
|
||||
OK no-verdict: exit 2, no-verdict
|
||||
OK changes-later: exit 2, not-approved
|
||||
OK short-commit: exit 2, not-approved
|
||||
OK wrong-task-body: exit 2, not-approved
|
||||
OK args: exit 2, decision-not-found
|
||||
OK earlier-merge: exit 0
|
||||
OK ref-moved: exit 2, ref-moved
|
||||
OK root-top: exit 2, root-file
|
||||
OK root-newdir: exit 2, root-file
|
||||
OK root-src: exit 2, root-file
|
||||
OK unrelated: exit 2, base-conflict
|
||||
OK empty: exit 2, base-conflict
|
||||
OK an empty reviewed diff says so
|
||||
OK dirty-changed: exit 2, checkout-dirty
|
||||
OK dirty-staged: exit 2, checkout-dirty
|
||||
OK dirty-untracked: exit 2, checkout-dirty
|
||||
OK conflict: exit 2, base-conflict
|
||||
OK a refusal after the worktree is added leaves no worktree or scratch
|
||||
OK cleanup looks for the worktree's Docker network
|
||||
OK dirty-during: exit 2, checkout-dirty
|
||||
OK a reviewed path dirtied during the gate moves no ref
|
||||
OK drift: exit 2, patch-drift
|
||||
OK patch-drift leaves no worktree or scratch
|
||||
OK gate-pkg: exit 2, gate-failed
|
||||
OK gate-failed names the failing step's log, and it exists
|
||||
OK gate-failed leaves no worktree or scratch
|
||||
OK gate-script: exit 2, gate-failed
|
||||
OK a failing scripts/test-*.sh fails the gate
|
||||
OK a failed gate moves no ref
|
||||
OK nomods: exit 4
|
||||
OK a target without node_modules leaves no worktree or scratch
|
||||
OK moved: exit 2, base-moved
|
||||
OK base-moved keeps the base where it moved to
|
||||
OK dry: exit 0
|
||||
OK --dry-run writes no ref
|
||||
OK --dry-run prints the commit with --subject and --attribution
|
||||
OK --dry-run runs the gate
|
||||
OK --dry-run leaves no worktree or scratch
|
||||
OK land: exit 0
|
||||
OK the landing is one commit on the old base
|
||||
OK its diff is the reviewed diff by patch-id
|
||||
OK its tree is the dry run's
|
||||
OK the message has the option's subject, the citations and the attribution last
|
||||
OK it prints the line for the PM
|
||||
OK the checked-out base has the reviewed paths and keeps the unrelated edit
|
||||
OK only refs/heads/refactor exists in the target
|
||||
OK the work ref is unchanged
|
||||
OK the bus database is unchanged
|
||||
OK the gate ran each package suite but queue, and each scripts/test-*.sh
|
||||
OK the landing leaves no worktree or scratch
|
||||
OK the target's node_modules is still there, with its contents
|
||||
OK the worktree's Docker network is removed
|
||||
---
|
||||
land-work selftests: 84 passed, 0 failed
|
||||
@@ -0,0 +1,36 @@
|
||||
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/gate-owner.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 156
|
||||
ℹ pass 156
|
||||
ℹ fail 0
|
||||
OK node --test packages/queue/tests/
|
||||
OK scripts/mosaic queue help
|
||||
skip queue verify and render --check: this checkout (/mnt/storage/src/mosaic-stack-rocko-114) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
|
||||
|
||||
queue suite: 28 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,32 @@
|
||||
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
|
||||
skip live recall pair: user recall run, recalled user name (docker daemon unavailable)
|
||||
OK no agent identity on headless run
|
||||
|
||||
selftest: 26 passed, 0 failed
|
||||
@@ -0,0 +1,13 @@
|
||||
✔ pinned Discord render/install and missing prerequisites; checkout bytes unchanged (35.745472ms)
|
||||
✔ Discord status reports effective WorkingDirectory from a fake user manager (28.458045ms)
|
||||
✔ check --offline validates local fixture without REST or gateway calls (107.822599ms)
|
||||
✔ check --offline refuses token mode and missing context under the selected repo (104.910772ms)
|
||||
✔ run --offline is a usage refusal before reading config or networking (51.506878ms)
|
||||
ℹ tests 5
|
||||
ℹ suites 0
|
||||
ℹ pass 5
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 335.898598
|
||||
@@ -0,0 +1,35 @@
|
||||
✔ pinned Discord render/install and missing prerequisites; checkout bytes unchanged (33.955405ms)
|
||||
✔ Discord status reports effective WorkingDirectory from a fake user manager (28.150537ms)
|
||||
✖ check --offline validates local fixture without REST or gateway calls (55.152759ms)
|
||||
✔ check --offline refuses token mode and missing context under the selected repo (107.793918ms)
|
||||
✔ run --offline is a usage refusal before reading config or networking (53.575105ms)
|
||||
ℹ tests 5
|
||||
ℹ suites 0
|
||||
ℹ pass 4
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 286.142494
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/discord/tests/offline-check.test.mjs:15:1
|
||||
✖ check --offline validates local fixture without REST or gateway calls (55.152759ms)
|
||||
AssertionError [ERR_ASSERTION]: discord: token file does not hold a bot token: /mnt/storage/scratch/tmp/mosaic-discord-VWXFQT/secrets/bot.token
|
||||
|
||||
|
||||
2 !== 0
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-114/packages/discord/tests/offline-check.test.mjs:16:56)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 2,
|
||||
expected: 0,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
✖ pinned Discord render/install and missing prerequisites; checkout bytes unchanged (11.395833ms)
|
||||
ℹ tests 1
|
||||
ℹ suites 0
|
||||
ℹ pass 0
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 75.471822
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/discord/tests/copy-service.test.mjs:11:1
|
||||
✖ pinned Discord render/install and missing prerequisites; checkout bytes unchanged (11.395833ms)
|
||||
AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
|
||||
|
||||
assert.ok(r.stdout.includes(`ExecStart=${process.execPath} ${s.copy}/packages/discord/src/cli.mjs run %i --supervised --repo ${s.copy}\n`))
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-114/packages/discord/tests/copy-service.test.mjs:13:70)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: false,
|
||||
expected: true,
|
||||
operator: '==',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
✖ pinned Discord render/install and missing prerequisites; checkout bytes unchanged (43.477288ms)
|
||||
ℹ tests 1
|
||||
ℹ suites 0
|
||||
ℹ pass 0
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 109.274458
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/discord/tests/copy-service.test.mjs:11:1
|
||||
✖ pinned Discord render/install and missing prerequisites; checkout bytes unchanged (43.477288ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
|
||||
0 !== 1
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-114/packages/discord/tests/copy-service.test.mjs:22:333)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 0,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
✖ check --offline validates local fixture without REST or gateway calls (54.337228ms)
|
||||
ℹ tests 1
|
||||
ℹ suites 0
|
||||
ℹ pass 0
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 120.395496
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/discord/tests/offline-check.test.mjs:15:1
|
||||
✖ check --offline validates local fixture without REST or gateway calls (54.337228ms)
|
||||
AssertionError [ERR_ASSERTION]: discord: GET /users/@me: fixture refuses network
|
||||
|
||||
|
||||
1 !== 0
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-114/packages/discord/tests/offline-check.test.mjs:16:56)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 1,
|
||||
expected: 0,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
[
|
||||
{
|
||||
"name": "checkout-execstart",
|
||||
"exit": 1,
|
||||
"status": "killed"
|
||||
},
|
||||
{
|
||||
"name": "missing-pi-accepted",
|
||||
"exit": 1,
|
||||
"status": "killed"
|
||||
},
|
||||
{
|
||||
"name": "offline-calls-getMe",
|
||||
"exit": 1,
|
||||
"status": "killed"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
Named network absent or Docker unavailable; dead-socket gate created none.
|
||||
@@ -0,0 +1,2 @@
|
||||
[]
|
||||
Error response from daemon: network mosaic-stack-rocko-114_default not found
|
||||
@@ -0,0 +1 @@
|
||||
busy: []
|
||||
@@ -0,0 +1,3 @@
|
||||
request rocko-114-review-1 not sent: MOSAIC_GITEA_CREDENTIAL_FILE is not set; a request posts only with rocko's own token file
|
||||
ok rocko-114-review-1 rev 1186 row 114 in-progress→in-review round 1 on #1608; request rocko-114-review-1 requesting
|
||||
ok rocko-114-review-1.outcome rev 1187 row 114 round 1 request rocko-114-review-1 failed
|
||||
@@ -0,0 +1,67 @@
|
||||
✖ check --offline validates local fixture without REST or gateway calls (53.152825ms)
|
||||
✖ check --offline refuses token mode and missing context under the selected repo (50.950038ms)
|
||||
✔ run --offline is a usage refusal before reading config or networking (49.833721ms)
|
||||
ℹ tests 3
|
||||
ℹ suites 0
|
||||
ℹ pass 1
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 224.637027
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/discord/tests/offline-check.test.mjs:15:1
|
||||
✖ check --offline validates local fixture without REST or gateway calls (53.152825ms)
|
||||
AssertionError [ERR_ASSERTION]: discord: unknown argument: --offline
|
||||
usage: mosaic-discord check <binding> [--config PATH] [--repo PATH]
|
||||
mosaic-discord run <binding> [--config PATH] [--repo PATH] [--supervised]
|
||||
mosaic-discord stop <binding> [--config PATH]
|
||||
mosaic-discord unlock <binding> [--config PATH]
|
||||
mosaic-discord recover <binding> [--config PATH]
|
||||
mosaic-discord reload <binding> [--config PATH]
|
||||
|
||||
|
||||
4 !== 0
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-114/packages/discord/tests/offline-check.test.mjs:16:56)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 0,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/discord/tests/offline-check.test.mjs:20:1
|
||||
✖ check --offline refuses token mode and missing context under the selected repo (50.950038ms)
|
||||
AssertionError [ERR_ASSERTION]: discord: unknown argument: --offline
|
||||
usage: mosaic-discord check <binding> [--config PATH] [--repo PATH]
|
||||
mosaic-discord run <binding> [--config PATH] [--repo PATH] [--supervised]
|
||||
mosaic-discord stop <binding> [--config PATH]
|
||||
mosaic-discord unlock <binding> [--config PATH]
|
||||
mosaic-discord recover <binding> [--config PATH]
|
||||
mosaic-discord reload <binding> [--config PATH]
|
||||
|
||||
|
||||
4 !== 2
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-114/packages/discord/tests/offline-check.test.mjs:21:95)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 2,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
✖ pinned Discord render/install and missing prerequisites; checkout bytes unchanged (5.455806ms)
|
||||
✖ Discord status reports effective WorkingDirectory from a fake user manager (30.187919ms)
|
||||
ℹ tests 2
|
||||
ℹ suites 0
|
||||
ℹ pass 0
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 100.729282
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/discord/tests/copy-service.test.mjs:11:1
|
||||
✖ pinned Discord render/install and missing prerequisites; checkout bytes unchanged (5.455806ms)
|
||||
AssertionError [ERR_ASSERTION]: usage: scripts/discord-service.sh render | install [--dir DIR] [--no-reload] | uninstall [--dir DIR] [--no-reload] | status <binding>
|
||||
|
||||
|
||||
4 !== 0
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-114/packages/discord/tests/copy-service.test.mjs:12:94)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 0,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/discord/tests/copy-service.test.mjs:23:1
|
||||
✖ Discord status reports effective WorkingDirectory from a fake user manager (30.187919ms)
|
||||
AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
|
||||
|
||||
assert.ok(r.stdout.includes(`copy: ${s.id}`))
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-114/packages/discord/tests/copy-service.test.mjs:24:337)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: false,
|
||||
expected: true,
|
||||
operator: '==',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
[
|
||||
{
|
||||
"suite": "node-discord",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:30:20.823230+00:00",
|
||||
"ended": "2026-10-14T20:30:23.493776+00:00",
|
||||
"summaries": [
|
||||
"\u2714 turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.649376ms)",
|
||||
"\u2714 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 (2.66673ms)",
|
||||
"\u2139 tests 183",
|
||||
"\u2139 pass 183",
|
||||
"\u2139 fail 0",
|
||||
"\u2139 cancelled 0",
|
||||
"\u2139 skipped 0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"suite": "test-auth",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:30:23.494015+00:00",
|
||||
"ended": "2026-10-14T20:30:24.109265+00:00",
|
||||
"summaries": [
|
||||
"selftest: 15 passed, 0 failed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"suite": "test-conductor",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:30:24.109530+00:00",
|
||||
"ended": "2026-10-14T20:30:30.287430+00:00",
|
||||
"summaries": [
|
||||
"OK failed run refused (exit 1)",
|
||||
"OK failed run: target untouched",
|
||||
"selftest: 17 passed, 0 failed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"suite": "test-config",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:30:30.287694+00:00",
|
||||
"ended": "2026-10-14T20:30:31.955466+00:00",
|
||||
"summaries": [
|
||||
"OK failed validation modified nothing",
|
||||
"selftest: 24 passed, 0 failed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"suite": "test-discord",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:30:31.955716+00:00",
|
||||
"ended": "2026-10-14T20:30:38.736178+00:00",
|
||||
"summaries": [
|
||||
"OK node --test packages/discord/tests/ (\u2139 pass 183)",
|
||||
"discord suite: 78 passed, 0 failed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"suite": "test-extension-package",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:30:38.736435+00:00",
|
||||
"ended": "2026-10-14T20:30:40.052900+00:00",
|
||||
"summaries": [
|
||||
"extension package selftest: 18 passed, 0 failed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"suite": "test-foundation",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:30:40.053145+00:00",
|
||||
"ended": "2026-10-14T20:31:17.005136+00:00",
|
||||
"summaries": [
|
||||
"OK node --test scripts/foundation/ (\u2139 pass 80)",
|
||||
"selftest: 44 passed, 0 failed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"suite": "test-land-work",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:31:17.005438+00:00",
|
||||
"ended": "2026-10-14T20:31:25.637399+00:00",
|
||||
"summaries": [
|
||||
"OK gate-pkg: exit 2, gate-failed",
|
||||
"OK gate-failed names the failing step's log, and it exists",
|
||||
"OK gate-failed leaves no worktree or scratch",
|
||||
"OK gate-script: exit 2, gate-failed",
|
||||
"OK a failed gate moves no ref",
|
||||
"land-work selftests: 84 passed, 0 failed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"suite": "test-queue",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:31:25.637691+00:00",
|
||||
"ended": "2026-10-14T20:31:45.992880+00:00",
|
||||
"summaries": [
|
||||
"\u2139 tests 156",
|
||||
"\u2139 pass 156",
|
||||
"\u2139 fail 0",
|
||||
"skip queue verify and render --check: this checkout (/mnt/storage/src/mosaic-stack-rocko-114) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)",
|
||||
"queue suite: 28 passed, 0 failed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"suite": "test-release",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:31:45.993134+00:00",
|
||||
"ended": "2026-10-14T20:31:46.157295+00:00",
|
||||
"summaries": [
|
||||
"skip state-machine cases (docker daemon unavailable)",
|
||||
"selftest: 4 passed, 0 failed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"suite": "test-task",
|
||||
"exit": 0,
|
||||
"began": "2026-10-14T20:31:46.157584+00:00",
|
||||
"ended": "2026-10-14T20:31:48.126073+00:00",
|
||||
"summaries": [
|
||||
"skip adapter seam cases (docker daemon unavailable)",
|
||||
"skip workspace/capability cases (docker daemon unavailable)",
|
||||
"skip live task cases (docker unavailable)",
|
||||
"skip live recall pair: user recall run, recalled user name (docker daemon unavailable)",
|
||||
"selftest: 26 passed, 0 failed"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,82 @@
|
||||
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/notify.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/copy-service.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/notify.test.mjs
|
||||
OK syntax: packages/discord/tests/offline-check.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 183)
|
||||
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
|
||||
OK pinned render runs copy CLI with copy cwd/repo and no checkout executable
|
||||
OK pinned install writes the rendered unit in a temporary unit directory
|
||||
OK pinned install refuses short prerequisite and writes no unit
|
||||
OK pinned install refuses missing prerequisite and writes no unit
|
||||
OK pinned install refuses cli prerequisite and writes no unit
|
||||
OK pinned install refuses pi prerequisite and writes no unit
|
||||
OK status names the copy from fake systemctl effective cwd
|
||||
OK checkout install explicitly recommends --copy
|
||||
OK wrapper check --offline passes local fixture and makes no network call
|
||||
OK wrapper run --offline refuses usage
|
||||
|
||||
discord suite: 78 passed, 0 failed
|
||||
@@ -0,0 +1,38 @@
|
||||
850f50e1672e1667b501e2f293f352b0fbf69d6124f7059f2d11e03735d7648a BUILD.md
|
||||
8c897b19b571ad5b0ea8ee3a28b0d9d339bb98126dd3ce5f3193e7e89a655f8f base.txt
|
||||
07d20b63826e25d5273175c298e00402810272e8d03d43748bf15b40de008dd2 build.patch
|
||||
68c60a79914692f3f6d4923f7bcff41de109ccca33bf6fba687fb88efa24bcac candidate-manifest.sha256
|
||||
80e206246f3e8b496f6c4b9744f50ccdb78a978178f12f3a60b70ca91a989064 files.txt
|
||||
8ad99be6e5418fbb45f3d2de9bc55dbda878f75fbddeec19b9a41557543b18af gate.py
|
||||
987ecd603283b2bed71f6703b6be4a4f6672e30b96b764602c3f13f945986b90 mutants.py
|
||||
088e0684151cd1d0b9d50577d1c481a805739e7084b58a61a241ab5c430be503 out/checkout-render-after.txt
|
||||
088e0684151cd1d0b9d50577d1c481a805739e7084b58a61a241ab5c430be503 out/checkout-render-before.txt
|
||||
8793c861ff5ca1ff86e7250d20a5e5406c579d106573421bd0a9509a5a00e502 out/final-verification.txt
|
||||
2ddefa97647c42b7fff1e9e1cfc0323e45a2994f4f505832b215b0ef8d1955dc out/full/gate-results.json
|
||||
666aaa26657e42f419872f00aa6232664a264ee681f207e25d05d4ab682db347 out/full/node-discord.txt
|
||||
f91fc95a173339137f981e24fbf630acfde957e32c3cd71e28304532083c6a77 out/full/test-auth.txt
|
||||
e1206366f07f3d9029530dcdcb55c0282d3f052adc60fc7fd51642d5d4ec3600 out/full/test-conductor.txt
|
||||
52d211444489ea729dafc3c9745d4474d9dfd50f41055cbad792424197f04073 out/full/test-config.txt
|
||||
87d2e24ce0be7586e31ea1f2c9ce5618b3127515b3c3cd3893f2a0ed899d5822 out/full/test-discord.txt
|
||||
7f08b2b9deb0549a13bd205f00284e3c42838ff7ede6c07d503b3466447152d6 out/full/test-extension-package.txt
|
||||
83dfaa21c4e4ad941088efab3f69e87ebd2f6bd3cc72ede938e21dc06da8a34f out/full/test-foundation.txt
|
||||
8e2254aa3e1bb9b205ab72c68273d447c0328c9f156ccba2a46f3a0476468b58 out/full/test-land-work.txt
|
||||
3faa89e75a914ce8d41980c71c54bdbe78ef1af764108088d3cae90a907e3730 out/full/test-queue.txt
|
||||
6183e6b9b05edb497e92ffd09a19bba49c6f656c750ecb2acc283021113d1a98 out/full/test-release.txt
|
||||
5dc16a91c36d6ff437e5fbb66764ece758e412f32f74b5a6d0c9cb9cad332f59 out/full/test-task.txt
|
||||
e6403893a1796778506f4979ae52349c183e846bd9e8ea89049dcd8a1997c862 out/green-final.txt
|
||||
d36abefd090b0e48ff62f4b12f32255367372cf1d59fcb1d6b26dcc72faf354e out/green.txt
|
||||
c5c3085c3cb096d3c8499907e780e818352387507869718c05e8e8466075cf0b out/mutants/checkout-execstart.txt
|
||||
8e8bb9e2af5da4a7905e5dc29bd299d546ef806039662ef8c662283d254dec98 out/mutants/missing-pi-accepted.txt
|
||||
d92b1ba65404e7fd0b0e59980ca6f4f2deb3e348cdf96276494b078467e83651 out/mutants/offline-calls-getMe.txt
|
||||
bee2f510b309a3af98d43f63e34384968065d7bdea5a786670bf3605a72eba47 out/mutants/summary.json
|
||||
48dbb5dcccd5415a1e1b39326081598f543198471420cb3f174ab9d19bb8b0b5 out/network-clear.txt
|
||||
0c50414f09a179e749639c37f45c13f746432213c0c8052660eda09158094303 out/network-inspect.txt
|
||||
7b1c894bf5ac4017fc38d5d07b725f71fa4f0edd11ff68028ed53b8e6a2263a9 out/process-clear.txt
|
||||
44267c81ad178dfd389b4d6deb3dc80176ad9fe828e7b6cce82c37e441beb52d out/queue-in-review.txt
|
||||
5f17c71b2638de539f996a7d00290516d39fe5a24734ae08b55ddad01bfd02d8 out/red-offline.txt
|
||||
b45024c5f50fc490553c1784c8975964cd2a240f5aa7e53f57a2924d80b12785 out/red-service.txt
|
||||
fcfbc35c0c256e7da6019994bdb4ee319f4379dcb5a27b6a2e8e68d818ba45a7 out/suite-counts.json
|
||||
87d2e24ce0be7586e31ea1f2c9ce5618b3127515b3c3cd3893f2a0ed899d5822 out/test-discord-first.txt
|
||||
b3dd66ba58d6a2c78156c2a766ab51b37a8b378847de76311a715d104e076865 process-sweep.py
|
||||
b0b8a95469e77280442d4733be76e0ed84ffd52f32b11c59780b968205685b06 verify.sh
|
||||
@@ -0,0 +1,13 @@
|
||||
"""Inspect only processes under this row's worktree; no unrelated argv/env."""
|
||||
from pathlib import Path
|
||||
import os,sys
|
||||
root='/mnt/storage/src/mosaic-stack-rocko-114';busy=[]
|
||||
for p in Path('/proc').iterdir():
|
||||
if not p.name.isdigit():continue
|
||||
try:
|
||||
cwd=os.readlink(p/'cwd')
|
||||
if cwd==root or cwd.startswith(root+'/'):
|
||||
busy.append((int(p.name),(p/'comm').read_text().strip(),cwd))
|
||||
except OSError:pass
|
||||
print('busy:',busy)
|
||||
sys.exit(bool(busy))
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
packet=/mnt/storage/src/mosaic-stack/agents/rocko/work/queue-114/r1
|
||||
[ -z "$(git branch --show-current)" ] || { echo 'detached worktree required' >&2;exit 2; }
|
||||
export DOCKER_HOST=unix:///nonexistent.sock
|
||||
sha256sum -c "$packet/candidate-manifest.sha256"
|
||||
git diff --check
|
||||
python "$packet/gate.py" "${1:?pass absolute gate output directory}"
|
||||
python "$packet/mutants.py" "${2:?pass separate absolute mutant output directory}"
|
||||
sha256sum -c "$packet/candidate-manifest.sha256"
|
||||
Reference in New Issue
Block a user