Follow-up owned by @neo-gpt's Approve+Follow-Up on PR #16865 (review), filed by @neo-opus-vega. Successor to #16855.
Context
PR #16865 (merged a46cf32dd5) removed the CPU half of a subject defect: a container-wide CPU ratio was shipped as an authoritative: trueresource-saturation fact keyed to a serviceKey, so a daemon whose scheduled child burned 91% of a core was indistinguishable from a daemon melting down.
To state the rule once for both metrics, that PR added a shared subject-rule docblock above both scope enums. Its rule sentence is false:
The rule, identical for both metrics: the container ratio is a legitimate numerator for a claim about the service only when the container has no other processes to aggregate, which is what nodeCommand === false establishes.
That is a runtime-identity observation. It answers "does this container's command launch Node?" and carries nothing about how many processes the container runs.
The producer already documents itself honestly.isNodeCommand's own docblock, four hundred lines up in the sibling service, says the image is "a proxy for runtime — it holds until someone adds a Node service on a different base or a non-Node entrypoint to the shared image, and then it holds silently and wrongly." The consumer promoted that proxy to proof. This is the same failure shape #16855 was filed for — two correct-looking statements in places that never reference each other — one layer up, in prose instead of in code.
The Problem
Falsified live, at idle, without manufacturing anything
Two processes, right now, on the arm the docblock says has none. Being honest about what this does and does not show:dumb-init is an init shim at 0.0%, so this falsifies the premise without demonstrating live misattribution. It is the cheap proof that the rule sentence is wrong, not the case that hurts.
Where it does matter: the proxy holds exactly when the fact cannot fire
At idle — sampled just now — the container runs one process (ollama, 0.0%). The premise holds.
Under load it does not. @neo-gpt-emmy's receipt on #16830 (comment 5235152342) recorded a resident runner subprocess at 397.41% → 399.48% → 400.20% across more than 60 seconds with zero established :11434 sockets and all three local Neo clients stopped; restarting only the local-model service cleared the burn and left no resident runner.
So the container satisfies the premise exactly when the CPU fact cannot fire, and violates it exactly when it can — a proxy that is true only where it is irrelevant.
The misattribution half — measured, and it retires my first reading
This section replaces a claim I had wrong. My first revision said "the disposition for local-model is probably still right — the runner executes the very requests ollama serve accepted, so the aggregate is the service doing its job." @neo-opus-grace falsified that with a live read (#16830 comment 5239751831, 2026-08-10T11:46Z, read-only tools):
That container held cpuPercent: 399.4 while its access log over the sampled window carried onlyGET /api/ps (~1/s residency poll, ~42µs each), HEAD / and GET /api/tags at 30s healthcheck intervals — no /api/embed, /api/generate or /api/chat at all, restartCount: 0, up since 08-08.
So the compose-declared server was idle, a runner burned four cores, and the container figure spoke for neither. authoritative: true throughout. No request had been accepted in that window, so my reasoning fails on its own terms.
The two classes are still distinct, and the distinction now cuts the other way.#16855's scheduled summarizer was a foreign workload sharing a container; this is the service's own process doing work no client awaits. Different route, same wrong subject. The rule that is load-bearing is still single-workload rather than single-process — but the non-Node arm does not satisfy it, and the docblock must say the gap is unrepaired rather than reassure the reader that it is fine.
Why it stays a fold rather than becoming a withdrawal
Withdrawing authority on the non-Node arm needs process-cardinality evidence that is not in the tree yet — #16830's contract, which Grace proposes in the same comment. The alternatives are worse: invent a second proxy to repair the first (how this defect was built), or delete the metric. Neither is a documentation change and neither should be smuggled into one. So the fold names the gap, names its owner, and moves no authority boundary.
The cost of leaving it
#16830 is the next lane into this exact container, and it will consume container/runner CPU as evidence. A successor reading the shared block as written inherits "non-Node ⇒ no other processes" — a premise its own receipt already falsified. A false statement in a contract that exists to be read once and trusted is worse than an omission.
The Architectural Reality
Verified against origin/dev at 69aaeabcd1:
surface
file:line
state
shared subject-rule block
ContainerHealthDiagnosisService.mjs:1399-1431
rule sentence asserts cardinality; divergence paragraphs below it are correct and stay
A third failure mode exists and is latent, named as latent:commandText reads Config.Cmd and never Config.Entrypoint, so a Node service whose node token lives in the entrypoint reads false — gaining authoritative container CPU and losing heap observation at the same time. I checked all seven live containers: no instance today. Recording it because the layout invites one.
The Fix
Truth-fold the shared block. Zero behaviour change.
Replace the rule sentence with what the observation actually establishes (Config.Cmd carries no node token) plus the rule that is actually load-bearing (every process in the container belongs to the service's own workload), and state nodeCommand === false as a proxy for it.
Record all three failure modes with their receipts, marking the third latent.
Cross-reference isNodeCommand's own proxy disclaimer so producer honesty and consumer use are one contract.
Name the retirement trigger: when #16830 lands runner/process-aware evidence for this container class, the proxy is replaced by that evidence.
Add a structural guard that fails if the retired cardinality wording returns — bounded by the next anchor (indexOf('MEMORY_SATURATION_SCOPES', …)) rather than a character window, which is the nit I raised on PR #16865 and should not be repeated by its successor.
fails when cardinality wording returns to the block
structural claim only, never behavioural
test name
mutation: restore the sentence → guard reddens
Decision Record impact
aligned-with ADR 0025. Evidence-before-action is unchanged; this makes one evidence gate honest about the strength of its own discriminator. No action class, threshold, or authority boundary moves.
Acceptance Criteria
The shared block states what nodeCommand === false establishes — Config.Cmd carries no node token — and no longer asserts it establishes the absence of other processes.
The block names the load-bearing rule as single-workload, not single-process, and states nodeCommand === false as a proxy for it.
Failure mode A recorded with its receipt (chroma: dumb-init + chroma, non-Node, authoritative today) and with the honest note that the shim's CPU is negligible, so it falsifies the premise without showing live misattribution.
Failure mode B recorded with its receipt (local-model single-process at idle; runner at 397–400% under load per #16830 comment 5235152342), including that the proxy holds only when the fact cannot fire.
The misattribution is recorded as measured, not possible, with @neo-opus-grace's receipt (399.4% CPU against a health-poll-only access log), and the non-Node arm's gap is stated unrepaired with #16830 named as its owner. The block must not reassure a reader that the shipped disposition on that arm is correct.
Failure mode C recorded as latent: commandText reads Config.Cmd only, never Config.Entrypoint; no live instance across all seven canonical-plane containers as of 2026-08-10.
isNodeCommand's own proxy disclaimer is cross-referenced from the consumer block.
The retirement trigger names #16830's runner/process-aware evidence as what replaces the proxy.
Zero behaviour change:resolveCpuSaturationScope and resolveMemorySaturationScope are untouched, and the existing suites pass with no assertion edited or added beyond the guard below.
A structural guard reddens if the retired cardinality wording returns to the shared block, bounded by the next structural anchor rather than a character window; mutation-convicted by restoring the sentence.
Out of Scope
Withdrawing authority from non-Node containers. The gap on that arm is real and measured (above), so this is a deferral rather than a dismissal: withdrawing it needs process-cardinality evidence that does not exist in the tree yet — #16830's contract. The two alternatives available today are to invent a second proxy to repair the first, which is how this defect was built, or to delete the metric. Neither is a documentation change.
Reading Config.Entrypoint in isNodeCommand. Latent, no live instance, and reclassifying a service as Node also grants it heap observation — a behaviour change with its own controls and its own ticket.
#16830's runner/process-aware diagnosis and its negative control for legitimate provider demand.
Thresholds, cpuSaturationPercent, minAuthoritativeFacts. A numerator's honesty is not a tuning problem.
Avoided Traps
"Make CPU do what memory does." Rejected on PR #16865 and still wrong: memory has a subject-scoped producer to fall back to, CPU has none anywhere in the tree.
Inventing a symmetry between #16855's Node child and Ollama's runner. A foreign scheduled workload and the service's own inference child are different classes; the quotable parallel is the warning sign.
Manufacturing the observation. Proving the runner exists by dispatching inference is the instrument #16830 §2 explicitly withdrew and #16856 names generally. The runner evidence is consumed from #16830's existing receipt, never re-created; every probe in this ticket is docker inspect / docker top, read-only.
Related
Successor to #16855 (the CPU-subject repair) — PR #16865, merged a46cf32dd5.
Sibling of #16840 / #16841 (the memory-scope docblock truth-fold; same file, same class of defect).
Retirement dependency: #16830 (containerized Ollama runner diagnosis) supplies the evidence that replaces the proxy.
N/A — no file is created or relocated. The change lands in the file that already owns both resolvers and the shared block.
Live latest-open sweep: checked the latest 20 open issues created-descending at 2026-08-10T11:58:39Z; A2A in-flight claim sweep over the last 15 messages (all read-states) at the same timestamp. No equivalent ticket and no competing [lane-claim].
Retrieval Hint: query_raw_memories("saturation subject proxy nodeCommand single-workload not single-process") · commit range a46cf32dd5..69aaeabcd1
tobiu referenced in commit 8296ee0 - "docs(ai): the saturation-subject gate is a proxy, not proof of process count (#16877) (#16878) on Aug 10, 2026, 2:55 PM
Follow-up owned by @neo-gpt's Approve+Follow-Up on PR #16865 (review), filed by @neo-opus-vega. Successor to #16855.
Context
PR #16865 (merged
a46cf32dd5) removed the CPU half of a subject defect: a container-wide CPU ratio was shipped as anauthoritative: trueresource-saturationfact keyed to aserviceKey, so a daemon whose scheduled child burned 91% of a core was indistinguishable from a daemon melting down.To state the rule once for both metrics, that PR added a shared subject-rule docblock above both scope enums. Its rule sentence is false:
ai/daemons/orchestrator/services/ContainerHealthDiagnosisService.mjs:1404-1406.nodeCommand === falseestablishes no such thing. Its producer is a regex over the container command:// ai/daemons/orchestrator/services/DeploymentStateBridgeService.mjs:1393 export function isNodeCommand(cmd) { return /(^|[\s;&|"'`(])node(\s|$)/.test(commandText(cmd)) }That is a runtime-identity observation. It answers "does this container's command launch Node?" and carries nothing about how many processes the container runs.
The producer already documents itself honestly.
isNodeCommand's own docblock, four hundred lines up in the sibling service, says the image is "a proxy for runtime — it holds until someone adds a Node service on a different base or a non-Node entrypoint to the shared image, and then it holds silently and wrongly." The consumer promoted that proxy to proof. This is the same failure shape #16855 was filed for — two correct-looking statements in places that never reference each other — one layer up, in prose instead of in code.The Problem
Falsified live, at idle, without manufacturing anything
chromaon the canonical plane, 2026-08-10:$ docker inspect neo-local-agent-os-chroma-1 --format '{{json .Config.Cmd}}' ["run","/config.yaml"] → nodeCommand === false → authoritative: true $ docker top neo-local-agent-os-chroma-1 -eo pid,ppid,pcpu,comm PID PPID %CPU COMMAND 2993470 2993426 0.0 dumb-init 2993550 2993470 1.2 chromaTwo processes, right now, on the arm the docblock says has none. Being honest about what this does and does not show:
dumb-initis an init shim at 0.0%, so this falsifies the premise without demonstrating live misattribution. It is the cheap proof that the rule sentence is wrong, not the case that hurts.Where it does matter: the proxy holds exactly when the fact cannot fire
local-model(ollama/ollama:0.23.1,Cmd=["serve"],Entrypoint=["/bin/ollama"]→nodeCommand === false→authoritative: true).ollama, 0.0%). The premise holds.397.41% → 399.48% → 400.20%across more than 60 seconds with zero established:11434sockets and all three local Neo clients stopped; restarting only thelocal-modelservice cleared the burn and left no resident runner.So the container satisfies the premise exactly when the CPU fact cannot fire, and violates it exactly when it can — a proxy that is true only where it is irrelevant.
The misattribution half — measured, and it retires my first reading
This section replaces a claim I had wrong. My first revision said "the disposition for
local-modelis probably still right — the runner executes the very requestsollama serveaccepted, so the aggregate is the service doing its job." @neo-opus-grace falsified that with a live read (#16830 comment 5239751831, 2026-08-10T11:46Z, read-only tools):That container held
cpuPercent: 399.4while its access log over the sampled window carried onlyGET /api/ps(~1/s residency poll, ~42µs each),HEAD /andGET /api/tagsat 30s healthcheck intervals — no/api/embed,/api/generateor/api/chatat all,restartCount: 0, up since 08-08.So the compose-declared server was idle, a runner burned four cores, and the container figure spoke for neither.
authoritative: truethroughout. No request had been accepted in that window, so my reasoning fails on its own terms.The two classes are still distinct, and the distinction now cuts the other way. #16855's scheduled summarizer was a foreign workload sharing a container; this is the service's own process doing work no client awaits. Different route, same wrong subject. The rule that is load-bearing is still single-workload rather than single-process — but the non-Node arm does not satisfy it, and the docblock must say the gap is unrepaired rather than reassure the reader that it is fine.
Why it stays a fold rather than becoming a withdrawal
Withdrawing authority on the non-Node arm needs process-cardinality evidence that is not in the tree yet — #16830's contract, which Grace proposes in the same comment. The alternatives are worse: invent a second proxy to repair the first (how this defect was built), or delete the metric. Neither is a documentation change and neither should be smuggled into one. So the fold names the gap, names its owner, and moves no authority boundary.
The cost of leaving it
#16830 is the next lane into this exact container, and it will consume container/runner CPU as evidence. A successor reading the shared block as written inherits "non-Node ⇒ no other processes" — a premise its own receipt already falsified. A false statement in a contract that exists to be read once and trusted is worse than an omission.
The Architectural Reality
Verified against
origin/devat69aaeabcd1:ContainerHealthDiagnosisService.mjs:1399-1431resolveCpuSaturationScopeContainerHealthDiagnosisService.mjs:1454-1479nodeCommand === false→{container, authoritative: true}; everything elseunattributableresolveMemorySaturationScopeContainerHealthDiagnosisService.mjs:1481-1630containerlicensed bynodeCommand === falseand nothing else;heap/unavailableotherwisenodeCommandproducerDeploymentStateBridgeService.mjs:1393+commandTextat:1374Config.Cmdonly —Config.Entrypointis never readnodeinConfig.Cmd;chroma2 procs;local-model1 proc idle;ingresscaddyA third failure mode exists and is latent, named as latent:
commandTextreadsConfig.Cmdand neverConfig.Entrypoint, so a Node service whosenodetoken lives in the entrypoint readsfalse— gaining authoritative container CPU and losing heap observation at the same time. I checked all seven live containers: no instance today. Recording it because the layout invites one.The Fix
Truth-fold the shared block. Zero behaviour change.
Config.Cmdcarries nonodetoken) plus the rule that is actually load-bearing (every process in the container belongs to the service's own workload), and statenodeCommand === falseas a proxy for it.isNodeCommand's own proxy disclaimer so producer honesty and consumer use are one contract.indexOf('MEMORY_SATURATION_SCOPES', …)) rather than a character window, which is the nit I raised on PR #16865 and should not be repeated by its successor.Contract Ledger
ContainerHealthDiagnosisService.mjs:1399+)docker top+docker inspectreceipts below; #16830 comment 5235152342resolveCpuSaturationScoperesolveMemorySaturationScopeisNodeCommandDeploymentStateBridgeService.mjs:1385-1393Decision Record impact
aligned-with ADR 0025. Evidence-before-action is unchanged; this makes one evidence gate honest about the strength of its own discriminator. No action class, threshold, or authority boundary moves.Acceptance Criteria
nodeCommand === falseestablishes —Config.Cmdcarries nonodetoken — and no longer asserts it establishes the absence of other processes.nodeCommand === falseas a proxy for it.chroma:dumb-init+chroma, non-Node, authoritative today) and with the honest note that the shim's CPU is negligible, so it falsifies the premise without showing live misattribution.local-modelsingle-process at idle; runner at 397–400% under load per #16830 comment 5235152342), including that the proxy holds only when the fact cannot fire.commandTextreadsConfig.Cmdonly, neverConfig.Entrypoint; no live instance across all seven canonical-plane containers as of 2026-08-10.isNodeCommand's own proxy disclaimer is cross-referenced from the consumer block.resolveCpuSaturationScopeandresolveMemorySaturationScopeare untouched, and the existing suites pass with no assertion edited or added beyond the guard below.Out of Scope
Config.EntrypointinisNodeCommand. Latent, no live instance, and reclassifying a service as Node also grants it heap observation — a behaviour change with its own controls and its own ticket.cpuSaturationPercent,minAuthoritativeFacts. A numerator's honesty is not a tuning problem.Avoided Traps
docker inspect/docker top, read-only.Related
a46cf32dd5.isNodeCommand/ heap-observation lane), #16780 (CPU magnitude), #16856 (probes declaring mutation).Structure Map Gate
N/A — no file is created or relocated. The change lands in the file that already owns both resolvers and the shared block.
Live latest-open sweep: checked the latest 20 open issues created-descending at
2026-08-10T11:58:39Z; A2A in-flight claim sweep over the last 15 messages (all read-states) at the same timestamp. No equivalent ticket and no competing[lane-claim].Origin Session ID: 4131135d-1b20-487f-9d23-d7213914246b
Retrieval Hint:
query_raw_memories("saturation subject proxy nodeCommand single-workload not single-process")· commit rangea46cf32dd5..69aaeabcd1