<feed xmlns='http://www.w3.org/2005/Atom'>
<title>newos4/modules/test/test-cryptogzip-guard.js, branch v4.6.168</title>
<subtitle>newOS4 - the browser terminal that runs WebX11 user spaces</subtitle>
<id>https://newos.webx11.duckdns.org/newos4/atom?h=v4.6.168</id>
<link rel='self' href='https://newos.webx11.duckdns.org/newos4/atom?h=v4.6.168'/>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/'/>
<updated>2026-11-12T15:24:15+00:00</updated>
<entry>
<title>fix(newos4): one arg-coercion boundary + binary-safe gunzip + clean boot log (v4.6.159)</title>
<updated>2026-11-12T15:24:15+00:00</updated>
<author>
<name>Hermes SysOp</name>
<email>sysop@webx11.duckdns.org</email>
</author>
<published>2026-11-12T15:24:15+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=c08a795ae822e905639caf563e36cf7953d530f1'/>
<id>urn:sha1:c08a795ae822e905639caf563e36cf7953d530f1</id>
<content type='text'>
1. Tool arguments are coerced once, at the dispatch boundary (module-aiassist.js)
   - _coerceToolArgs(name, a) at :1584 reads the tool's own JSON schema and, for every
     declared string prop, converts number/boolean with String() and object/array with
     JSON.stringify (never "[object Object]"); declared integer/number props accept
     unambiguous numeric strings. null/undefined stay absent so the handlers' defaults
     and _pathArg still own "no value given".
   - Called at both dispatch boundaries: runTool (:2650) and runModuleTool (:3565 —
     reachable from view_module and from ship_module -&gt; test_module/health_check, which
     have their own parse+switch).
   - A JSON body of "null" or "[1,2]" parses to a non-object; both sites normalise to {}
     before any handler reads a.field.
   - The 26 handlers keep their `a.x || ''` reads: `|| ''` catches only FALSY, which is
     how a truthy number or object reached .trim()/.split()/.replace() and filed one
     auto-report per tool (user_get "key.split is not a function", create_skill,
     patch_skill, delegate_task "task.substring is not a function", create_mindmap,
     web_fetch, list_skills, ship_module).

2. gunzip is binary-safe (cryptogzip.js — plan 2026-11-03 T3, the last open task)
   - _cmdGunzip used _vfsRead + _gunzipString (base64-only), so a real .gz fetched with
     wget reported "not valid base64 input (illegal character \u001f)" and could not be
     decompressed at all. It now reads bytes (_vfsReadBytes handles both the module's
     base64 output and raw Latin-1 bytes), checks the 1f 8b magic, and reports
     "not a gzip file" for non-gzip input without writing or deleting anything.
     The module's own gzip output still round-trips.

3. Boot log is clean (terminal.js)
   - Seven _safeRegister() calls for Editor/Shell/Alias/Finder/FileManager/Diff/BusyBox
     were guaranteed no-ops: those classes are declared in critical-ext-bundle.js, a
     &lt;script&gt; after module-loader.js, so they do not exist yet when new WebX11Terminal()
     runs; adding them to the registry map would still resolve to undefined. Removed with
     a comment — the ext bundle registers each one on load.
   - SimpleGUIModule IS a core class but was missing from _safeRegister's registry map,
     so the fallback could never restore it. Added.

4. New guard: admin/scripts/verify-plan-age.py + profile wrapper plan-age-guard.sh
   (cron newos4-plan-age-guard, no_agent, daily 07:10). Silent when healthy; reports
   plans marked &lt;!-- autonomy: review --&gt; with no execution stamp older than 72 h —
   the process gap that let four fix plans sit for six days before v4.6.158. Read-only
   by construction (148 real plans byte+mtime identical before/after a run), rc=2 when
   the directory cannot be scanned, --strict opt-in so a report is never recorded as a
   crashed guard.

Verified: 37/37 node tests, 3 new test files and 631 new assertions (613 arg-coercion,
18 gunzip); test-cryptogzip-guard's C8 updated because it pinned the old misleading
"not valid base64" message. Independent orchestrator checks: all 66 declared string
props across 58 tool defs coerced under 4 hostile shapes; positive cases untouched
(string content not double-encoded, numbers still numbers, null still absent, unknown
tool identity preserved). Served bytes byte-identical for core-bundle.js, main.html and
cryptogzip.js. Live Chromium on v4.6.159: write_file with object content wrote 27 chars
of readable JSON (not "[object Object]"), read_file with offset:"0"/limit:"200" coerced
to numbers, user_set with key 12345 -&gt; "12345" and object value -&gt; JSON, and the boot
log now carries 0 SafeRegister warnings (52 messages, 0 JS errors) with SimpleGUIModule
registered.
</content>
</entry>
<entry>
<title>fix(newos4): six crash classes from the feedback queue (v4.6.158)</title>
<updated>2026-11-09T22:58:35+00:00</updated>
<author>
<name>Hermes SysOp</name>
<email>sysop@webx11.duckdns.org</email>
</author>
<published>2026-11-09T22:58:35+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=9f6744dd5cb99b904f9a23fce6f2d98e8332bbf8'/>
<id>urn:sha1:9f6744dd5cb99b904f9a23fce6f2d98e8332bbf8</id>
<content type='text'>
Each item is symptom -&gt; cause -&gt; fix, re-verified against the served bytes.

1. debug-ai panel threw "Cannot read properties of undefined (reading 'substring')"
   (RPT-20261108-231622). _updateDebugPanel and two sibling previews called
   JSON.stringify(entry.args).substring(); JSON.stringify(undefined) returns
   undefined, and _toolCallLog entries carry args: undefined for any tool call
   with no arguments. All three previews now go through _formatToolArgs(), which
   renders "null" and never throws.

2. test_module threw "Cannot convert undefined or null to object"
   (RPT-20261108-232127). Object.keys(instance.commands) assumed every module
   initialises a command map. The throw landed after loadModuleFromCode had
   already succeeded, so the rolledBack:true branches were skipped and an
   unverified module stayed loaded. Both sites now require instance.commands.

3. list_files threw "originalPath.replace is not a function" (RPT-20261109-172742,
   3 occurrences in errors.json). A model can send a non-string path despite the
   schema. The path family (list_files/read_file/write_file/make_directory/
   delete_file) now coerces through _pathArg() and answers the tool's own honest
   error instead of a TypeError.

4. wpm search failed for EVERY keyword (RPT-20261109-123907): packages.json entry
   `kanban` used `description` while the other 31 use `desc`, and search() called
   pkg.desc.toLowerCase() mid-loop. Catalog reads now go through _pkg() /
   _packageMap() (desc || description, values coerced to strings), and the kanban
   entry carries name/version/desc/commands. Re-running the original loop for
   pdf/git/zzz is clean.

5. wx/webxai/webx turned a chat-lock timeout into an unhandled rejection
   (RPT-20261107-150726): forwardToAI() called chat(args) in a synchronous
   function without returning or catching the promise, so the 10 s busy throw
   never reached dispatchCommand's catch. It now returns the promise with a
   .catch that prints "AI error: ..." through terminal.print — console.error is
   hooked by the debug supervisor and would re-file the same report.

6. CryptoGzip._base64ToBytes threw a raw DOMException on non-base64 input
   (RPT-20261102-203616). It now validates first and throws
   Error("not valid base64 input (&lt;reason&gt;)"). The duplicate shadowing
   definitions at the bottom of the class were deleted — the later copy is the
   one that carried the unguarded atob().

Also regenerated modules/core/core-bundle.js (generated, never hand-edited) and
bumped the build v4.6.157 -&gt; v4.6.158 (main.html x3 + module-loader.js).

Verified: 35/35 node tests, including 6 new regression tests each proven red
against the pre-fix source; an independent orchestrator harness 36/36; and
newos4-maintain.py --fix all PASS after regenerating the stale changelog page.
Served bytes are byte-identical for the bundle, main.html and both ext modules,
and a live headless-Chromium run against v4.6.158 shows WpmModule.search('pdf')
returning 1 result, the debug panel rendering a no-arg entry as "null",
_base64ToBytes('grüße') raising a plain Error that names base64, and 0 JS errors
on load.

m.pull (RPT-20261102-211438) is closed as a usage error: no module in the tree
defines pull(); the real interfaces are the webdav-pull and model-pull commands.
</content>
</entry>
</feed>
