@ouro.bot/cli 0.1.0-alpha.842 → 0.1.0-alpha.844
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.json +13 -0
- package/deploy/unraid/Dockerfile +4 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/daemon/sanctuary-authority-installation.js +13 -4
- package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +22 -3
- package/dist/heart/turn-context.js +8 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.844",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Sanctuary authority start is faster and no longer blind (D-036): package verification walks the tree once without following symlinks and then reads each file with O_NOFOLLOW, instead of resolving every file's real path (on Unraid that was two-thirds of each pass: about 15 s instead of about 47 s per pass, and a gateway start runs two), and the gateway's own output now goes to a root-only, size-capped gateway.log in the authority directory instead of nowhere.",
|
|
8
|
+
"Sanctuary: the agent's own view of its senses reports the tokenless root-authority Telegram as ready (it said needs_config while Telegram worked), and a token found in the resident's config as needs_config, matching the daemon's rule (D-047)."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"version": "0.1.0-alpha.843",
|
|
13
|
+
"changes": [
|
|
14
|
+
"Butler image: drop dependency source maps, type declarations and Markdown from node_modules (about 11,800 of 28,000 files). The root authority pins and hashes every package file at each gateway start, which took minutes on Unraid's array (D-036)."
|
|
15
|
+
]
|
|
16
|
+
},
|
|
4
17
|
{
|
|
5
18
|
"version": "0.1.0-alpha.842",
|
|
6
19
|
"changes": [
|
package/deploy/unraid/Dockerfile
CHANGED
|
@@ -25,6 +25,10 @@ RUN npm ci --omit=dev --legacy-peer-deps --workspaces=false --no-audit --no-fund
|
|
|
25
25
|
&& rm -rf node_modules/better-sqlite3/build \
|
|
26
26
|
&& test -x /opt/ouro/node_modules/.bin/bw \
|
|
27
27
|
&& ln -s /opt/ouro/node_modules/.bin/bw /usr/local/bin/bw
|
|
28
|
+
# The runtime never reads dependency source maps, type declarations or Markdown, and the
|
|
29
|
+
# root authority pins and hashes every package file each time the gateway starts, which
|
|
30
|
+
# took minutes on Unraid's array (D-036). Dropping them removes about 40% of the files.
|
|
31
|
+
RUN find /opt/ouro/node_modules -type f \( -name '*.map' -o -name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' -o -name '*.md' -o -name '*.markdown' \) -delete
|
|
28
32
|
COPY dist ./dist
|
|
29
33
|
COPY assets ./assets
|
|
30
34
|
COPY skills ./skills
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0"?>
|
|
2
2
|
<Container version="2">
|
|
3
3
|
<Name>ouro-butler</Name>
|
|
4
|
-
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.
|
|
4
|
+
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.844</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
|
@@ -58,8 +58,12 @@ function directory(filePath, uid, gid, mode) {
|
|
|
58
58
|
throw new Error("Sanctuary authority installation directory metadata changed");
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
/** `trustedParent`: the caller already walked every parent directory without following
|
|
62
|
+
* symlinks, so only the leaf needs guarding, and O_NOFOLLOW does that. Resolving each
|
|
63
|
+
* package file's real path cost two-thirds of every verification on Unraid (D-036). */
|
|
64
|
+
function readOwned(filePath, uid, gid, mode, trustedParent = false) {
|
|
65
|
+
if (!trustedParent)
|
|
66
|
+
canonical(filePath);
|
|
63
67
|
const descriptor = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
64
68
|
try {
|
|
65
69
|
const stat = fs.fstatSync(descriptor);
|
|
@@ -102,8 +106,6 @@ function verifySanctuaryAuthorityInstallation(input) {
|
|
|
102
106
|
|| typeof pin.digest !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(pin.digest) || ![0o600, 0o644, 0o700, 0o755].includes(pin.mode)) {
|
|
103
107
|
throw new Error("Sanctuary authority package file pin is invalid");
|
|
104
108
|
}
|
|
105
|
-
if (digest(readOwned(path.join(input.packageRoot, relative), uid, gid, pin.mode)) !== pin.digest)
|
|
106
|
-
throw new Error("Sanctuary authority package file digest changed");
|
|
107
109
|
}
|
|
108
110
|
const actual = [];
|
|
109
111
|
const walk = (relative) => {
|
|
@@ -125,6 +127,13 @@ function verifySanctuaryAuthorityInstallation(input) {
|
|
|
125
127
|
walk("");
|
|
126
128
|
if (JSON.stringify(actual.sort()) !== JSON.stringify(expected))
|
|
127
129
|
throw new Error("Sanctuary authority package inventory changed");
|
|
130
|
+
// The walk above reached every file through real (not symlinked) directories, so a
|
|
131
|
+
// file read only needs to refuse a symlinked leaf.
|
|
132
|
+
for (const relative of expected) {
|
|
133
|
+
const pin = manifest.files[relative];
|
|
134
|
+
if (digest(readOwned(path.join(input.packageRoot, relative), uid, gid, pin.mode, true)) !== pin.digest)
|
|
135
|
+
throw new Error("Sanctuary authority package file digest changed");
|
|
136
|
+
}
|
|
128
137
|
const controllers = ["cpu", "memory", "pids"];
|
|
129
138
|
const readControl = (name) => fs.readFileSync(path.join(input.cgroupRoot, name), "utf8").trim();
|
|
130
139
|
for (const name of ["cgroup.controllers", "cgroup.subtree_control"]) {
|
|
@@ -73,6 +73,7 @@ const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
|
73
73
|
// page cache on Unraid's array that takes minutes, so readiness gets a generous budget
|
|
74
74
|
// (a 120 s budget failed a real rollback on 2026-09-24).
|
|
75
75
|
const GATEWAY_READY_TIMEOUT_MS = 900_000;
|
|
76
|
+
const GATEWAY_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
76
77
|
const TEMPLATE_JOURNAL = "/boot/config/custom/ouro-butler/docker-man-template-transaction.json";
|
|
77
78
|
// In-place upgrade of an installed authority: same epoch (token, issuer, cursor,
|
|
78
79
|
// gateway state), new reviewed package and resident image. Every root record it
|
|
@@ -536,9 +537,16 @@ class SanctuaryAuthorityRootLifecycle {
|
|
|
536
537
|
throw new Error("Sanctuary root token custody is not exclusive");
|
|
537
538
|
if (this.#pid() === null) {
|
|
538
539
|
this.#remove(`${this.#epochRoot()}/readiness.json`);
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
540
|
+
const log = this.#gatewayLog();
|
|
541
|
+
let child;
|
|
542
|
+
try {
|
|
543
|
+
child = (0, node_child_process_1.spawn)(this.#p("/usr/local/bin/node"), [this.#p(`${ROOT}/package/dist/heart/daemon/sanctuary-telegram-authority-entry.js`), "--config", this.#p(`${ROOT}/active.json`)], {
|
|
544
|
+
cwd: "/", env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" }, detached: true, stdio: ["ignore", log, log],
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
finally {
|
|
548
|
+
fs.closeSync(log);
|
|
549
|
+
}
|
|
542
550
|
child.unref();
|
|
543
551
|
let failed = false;
|
|
544
552
|
child.once("error", () => { failed = true; });
|
|
@@ -551,6 +559,17 @@ class SanctuaryAuthorityRootLifecycle {
|
|
|
551
559
|
else
|
|
552
560
|
await this.#wait(() => this.#ready(), GATEWAY_READY_TIMEOUT_MS);
|
|
553
561
|
}
|
|
562
|
+
/** The gateway's own output, kept root-only beside the token it guards and capped at two
|
|
563
|
+
* generations. It used to go nowhere, so a gateway that exited at startup said nothing
|
|
564
|
+
* (D-036: finding "host cgroup entry is invalid" took a hand-run gateway). */
|
|
565
|
+
#gatewayLog() {
|
|
566
|
+
const file = this.#p(`${ROOT}/gateway.log`);
|
|
567
|
+
if (fs.existsSync(file) && fs.statSync(file).size > GATEWAY_LOG_MAX_BYTES)
|
|
568
|
+
fs.renameSync(file, `${file}.1`);
|
|
569
|
+
const fd = fs.openSync(file, fs.constants.O_WRONLY | fs.constants.O_APPEND | fs.constants.O_CREAT | fs.constants.O_NOFOLLOW, 0o600);
|
|
570
|
+
fs.fchmodSync(fd, 0o600);
|
|
571
|
+
return fd;
|
|
572
|
+
}
|
|
554
573
|
#target() {
|
|
555
574
|
const target = this.#containers().find((container) => container.Name === "/ouro-butler");
|
|
556
575
|
if (!target || target.Image !== this.#transaction.targetImageId || target.Config.User !== "10001:10001"
|
|
@@ -212,9 +212,14 @@ function readSenseStatusLines() {
|
|
|
212
212
|
? openAIRealtimeVoiceReady
|
|
213
213
|
: cascadeVoiceReady,
|
|
214
214
|
a2a: true,
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
215
|
+
// Sanctuary's resident is deliberately tokenless: the root authority gateway holds the
|
|
216
|
+
// token, so a token in the resident's config would be the misconfiguration. Mirror the
|
|
217
|
+
// daemon's rule (sense-manager) instead of reporting a working sense as needs_config (D-047).
|
|
218
|
+
telegram: agentName === "sanctuary"
|
|
219
|
+
? runtimeConfig.ok && machineRuntimeConfig.ok && !Object.hasOwn(runtimePayload, "telegramBotToken") && !Object.hasOwn(machinePayload, "telegramBotToken")
|
|
220
|
+
: hasTextField(runtimePayload, "telegramBotToken")
|
|
221
|
+
&& hasTextField(runtimePayload, "telegramAuthorizedUserId")
|
|
222
|
+
&& hasTextField(runtimePayload, "telegramAuthorizedChatId"),
|
|
218
223
|
workbench: false,
|
|
219
224
|
};
|
|
220
225
|
const rows = [
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.844",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@ouro.bot/cli",
|
|
9
|
-
"version": "0.1.0-alpha.
|
|
9
|
+
"version": "0.1.0-alpha.844",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|