<feed xmlns='http://www.w3.org/2005/Atom'>
<title>newos4/modules/core/module-billing.js, branch main</title>
<subtitle>newOS4 - the browser terminal that runs WebX11 user spaces</subtitle>
<id>https://newos.webx11.duckdns.org/newos4/atom?h=main</id>
<link rel='self' href='https://newos.webx11.duckdns.org/newos4/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/'/>
<updated>2026-11-06T10:45:21+00:00</updated>
<entry>
<title>release(v4.6.150): commit the module work that has been live but uncommitted</title>
<updated>2026-11-06T10:45:21+00:00</updated>
<author>
<name>Agando</name>
<email>roduccomm@gmail.com</email>
</author>
<published>2026-11-06T10:45:21+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=03143a6e88832d06c7b08cdbf8de96ba79bda7d6'/>
<id>urn:sha1:03143a6e88832d06c7b08cdbf8de96ba79bda7d6</id>
<content type='text'>
Operator commit-approve, 2026-11-06. These changes have been served since
2026-11-02/03 while git HEAD still stamped v4.6.146, so the mirror published a
tree four builds behind the running instance and the release tag named the
wrong build.

module-orchestrator.js — orchestrate-parallel reported success it could not
prove. Results are now recorded by sub-task id through a resultSink handed to
delegateTask (harvest matched by currentTask/lastTask strings, which a later
delegation on the same agent overwrote, losing finished work as "agent not
found"); the tally counts summaries with status ok instead of
Promise.allSettled resolutions (a spawn refusal fulfils with undefined and used
to print green); and a busy agent is no longer reused at pool cap, where two
siblings erased each other's lease. Guard:
modules/test/test-orchestrator-parallel-harvest.js (16 cases, reproduces the
one-agent three-sub-task cap case that printed 3/3 succeeded).

module-billing.js — the balance said "not signed in — newOS4 sends no session
token", which was already false: the browser sends the HttpOnly cookie and
parse_bearer_token() falls back to it. `cost` is on by default (the flag was
opt-in and nobody knew the command existed; `cost off` opts out) and the
command now prints today's platform spend from GET /api/billing/usage?days=1
alongside the session estimate.

modules/ext/webdav.js — the inline WebView detector missed "Version/4.0"
Android WebViews, including the operator's own phone, so the login offered a
Telegram widget that cannot complete there. It now delegates to
modules/wx11-tg-webview.js when the page loaded it and keeps a matching
fallback (wv token, Version/4.0-Android, Telegram, FBAN/Instagram/Line).

modules/ext/media.js — text analysis model renamed deepseek-v4-flash -&gt;
deepseek-v4.1-flash in both the field and the printed label.

main.html + module-loader.js — v4.6.146 -&gt; v4.6.150 in LATEST_VER and the three
asset queries; core-bundle.js rebuilt (rebuild-bundles.py reports it already
in sync, 1,250,569 bytes).

Verified: node --check on all six scripts and the new test; the 16-case
orchestrator guard passes against the deployed bundle (ORCH_SRC=...); bundle
rebuild is a no-op; core-bundle.js?v=v4.6.150 answers 200.
</content>
</entry>
<entry>
<title>feat(billing): no model-class gate; free allowance becomes $0.10/day</title>
<updated>2026-11-01T17:44:26+00:00</updated>
<author>
<name>Agando</name>
<email>roduccomm@gmail.com</email>
</author>
<published>2026-11-01T17:44:26+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=bdd9d1fe8861cbee78b28384713ba150c2baa309'/>
<id>urn:sha1:bdd9d1fe8861cbee78b28384713ba150c2baa309</id>
<content type='text'>
Operator policy 2026-11-01: every subscription may call any model, and the free tier resets daily.

Model gate removed (api/ai-gateway.py)
  `plan == 'free' and model_weight &gt; 1 -&gt; 403 "Advanced models require Pro"` ran before any money
  logic, so a free-plan account holding $1086 of purchased credit was refused and told to upgrade.
  A model tier is not something we sell: the allowance and the USD balance bound spend. A caller who
  cannot pay now gets the honest 429 (with period + needed amount). model_weight survives only for
  track_usage() analytics.

Free allowance: $0.50/month -&gt; $0.10/day
  ~$3/month retail = $2.14/month of provider cost at 1.4x markup, ~6x the previous giveaway and
  still bounded per member (the 50-slot invite cap bounds the total; at full occupancy ~$107/mo or
  $2.14/user at 100% utilisation).

The window is policy, not code:
  - admin/data/pricing-policy.json: free_tier_usd=0.1, free_tier_period="day"
  - wx11_billing: FREE_TIER_PERIOD, allowance_window(plan) (day for free, month for paid),
    period_start(window), spend_usd(session, window) — one definition used by BOTH the gateway and
    the billing API (the old month_spend_usd duplicate is what let them drift before)
  - gateway + /api/billing/subscription now report the same window; plan_allowance gains `period`
  - dashboard.html, newOS4 module-billing, chatstudio account panel say "today" when period=day
  - published copy: index.html, pricing.html (card + compare table + how-usage-works), i18n planFree1,
    admin/docs/ai-gateway.html
  - verify-public-claims.py asserts the number AND the period per plan: a right number on the wrong
    window is the same defect as a wrong number

newOS4 bundle rebuilt (module-billing), LATEST_VER v4.6.135 -&gt; v4.6.136, main.html cache keys bumped.

Proof:
  - verify-public-claims.py rc=0 (copy == policy == live billing API, including periods)
  - /api/billing/subscription: limit_usd 0.1, period "day", remaining 0.1
  - gateway: gemma4 200, kimi-k2-thinking 502 (gate gone; fake model =&gt; upstream 404)
  - smoke-auth PASS, smoke-gateway PASS (limit=$0.1 in the payload check)
  - live pages serve "$0.10 of AI usage / day", zero $0.50 left on index/pricing/subscriptions
  - allowance math: free cap_cost $0.0714/day, pro $7.7779/mo, team $23.35/mo, ent $77.85/mo
</content>
</entry>
<entry>
<title>fix(newos4): pipe stage quoting, maintain ordering, help cost row, plan status (v4.6.131)</title>
<updated>2026-10-29T11:42:30+00:00</updated>
<author>
<name>Agando</name>
<email>roduccomm@gmail.com</email>
</author>
<published>2026-10-29T11:42:30+00:00</published>
<link rel='alternate' type='text/html' href='https://newos.webx11.duckdns.org/newos4/commit/?id=320877d2c8a3bb6be3d32780ecfc6e7d14528f66'/>
<id>urn:sha1:320877d2c8a3bb6be3d32780ecfc6e7d14528f66</id>
<content type='text'>
Four items closed in one batch; three ran as parallel subagents on disjoint files, the bundle was
rebuilt once by the parent (rebuild-bundles.py serialises the working tree, which carries other
sessions' work — see 161372f).

1. PIPE STAGES KEEP THEIR QUOTES. `js "a b".length | wc -c` reached eval as `a b.length` and printed
   a ReferenceError. terminal.js executePipeChain() now keeps `stages[i]` aligned 1:1 with
   `pipeParts[i]` (the `.filter(argv =&gt; argv.length &gt; 0)` shifted the index; replaced by a lastStage
   scan + `if (argv.length === 0) continue`) and forwards the stage's own text to the dispatcher.
   The core path alone was dead code: ShellModule.init() replaces terminal.executePipeChain
   (shell.js:144), and that override is what runs in the browser (`executePipeChain.toString()`
   contained `cmdText.split`, not `lastStage`). Both now pass `cmdText`/`pipeParts[i]`.
   test-pipe-guard.js was rewritten for the new shape and now also asserts the shell override
   forwards raw text — the assertion that would have caught the shadowing. RED on HEAD: both flags
   false; GREEN now.

2. MAINTENANCE --fix CAN CLEAR ITS OWN WARN. check_index() ran before check_atlas(), which is what
   regenerates course/atlas.json, so --fix rebuilt modules-index.json from the stale atlas and the
   same WARN reappeared every run. check_atlas() now runs immediately before check_index().
   modules-index.json regenerated: cost/usage present, 349 commands, version 2026-10-29-atlas.

3. `cost` DOCUMENTED. modules/core/module-billing.js registers `cost [on|off]` (an alias of `usage`);
   help.html had no row. Added above the `usage` row. help coverage 362/363 -&gt; 363/363.

4. SHIPPED PLANS STOP ASKING FOR A DECISION. The applier only flips review&lt;-&gt;safe, so every executed
   review-gated plan kept counting as an open decision (5 of 5). newos4-feedback-collect.py now reads
   the body: a `## Status` / `## Implementation` heading followed by shipped|executed plus a version,
   commit or date is `status: done`; those stay in `plans` (visible as shipped) but leave
   plans_review and awaiting_decision. Counts: plans_review 5 -&gt; 0, awaiting_decision 4 -&gt; 1
   (the remaining one is a real open report, RPT-20261027-160751).
   admin/feedback.html read `it.status[0]`, which would render `d` for a string status; it now
   prefers `status_lines[0]`.

Also carried, authored by earlier uncommitted sessions and already live in the bundle (attributed,
not silently absorbed): modules/core/module-billing.js (new, BillingModule: cost/usage),
its CORE_FILES entry in scripts/rebuild-bundles.py, and the BillingModule registry entry in
terminal.js. The bundle was rebuilt from HEAD + this batch with ollama-manager.js taken from HEAD,
so another session's in-flight `_httpError` refactor is deliberately NOT in the shipped bundle
(grep -c _httpError core-bundle.js = 0).

Verification
  node modules/test/test-pipe-guard.js   -&gt; {"empty":[],"blank":[],"classifyGuarded":true,"pipeGuarded":true,"shellForwardsRaw":true}
  node modules/test/test-parse-args.js   -&gt; 28/28 pass
  maintained job                          -&gt; node tests 21/21 pass
  scripts/newos4-maintain.py             -&gt; maintenance result: OK (modules-index 349, help 363/363,
                                            changelog current v4.6.131)
  live v4.6.131 (real DOM, no print monkey-patch):
    js "a b".length | wc -c   -&gt; 1        (js printed 3, one byte)
    js "a b".length           -&gt; 3
    js "abc".toUpperCase()    -&gt; ABC
    echo "two words"          -&gt; two words
    node -e "import('fs')"    -&gt; red error + Node-builtin hint
    js 6*7                    -&gt; 42
    unhandledrejection        -&gt; 0
  livePipeImpl: shell-override, livePipeForwardsRaw: true, costRegistered: true
</content>
</entry>
</feed>
