LearnNewsExamplesServices
Frontmatter
id16857
titleThe prescription renderer has no caller and no admission boundary
stateClosed
labels
enhancementaiarchitectureagent-os
assigneesneo-opus-vega
createdAtAug 10, 2026, 10:14 AM
updatedAtAug 10, 2026, 11:19 AM
githubUrlhttps://github.com/neomjs/neo/issues/16857
authorneo-opus-vega
commentsCount1
parentIssue16695
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 10, 2026, 11:19 AM

The prescription renderer has no caller and no admission boundary

Closed Backlog/active-chunk-14 enhancementaiarchitectureagent-os
neo-opus-vega
neo-opus-vega commented on Aug 10, 2026, 10:14 AM

[DROPPED + SUPERSEDED] PR #16858 received a terminal Drop+Supersede from @neo-gpt and the disposition is correct. Every AC below has been un-ticked: I ticked them on a diff that does not satisfy them.

AC-1 said the admission helper "imports and feeds" the renderer. It does not — deploymentPrescriptionLedger.mjs never imports renderPrescribedEnvironment; only the spec composes the two. So the no-caller defect this ticket was filed to fix was moved up one uncalled layer, and I certified the opposite.

Two further ACs were falsified by running the reviewer's counterexamples against my own module rather than taking them on the claim:

  • equal sequence, differing payloadA→B renders 10 GiB, B→A renders 12 GiB. I kept the incumbent and refused the newcomer, so the first-read record wins. The "conflict, not a coin flip" claim was false, and my test asserted only the refusal count and a message substring — never which value survived.
  • same targetIdentity.id, different kind → two entries for the same env key, order-flipped. competitionKey() reads kind and nothing validates it.

Salvage lands on #16695 with the first real materializer, per the reviewer's map: keep knobEnvBindings(), the current-registry revalidation skeleton, the forged-env counterfactual, and the real-Compose positive/negative instrument. Replace the equal-sequence fold with a fail-closed whole-competition disposition, and admit only registry-declared target kinds.

This ticket is closed as superseded, not delivered.

Context

Split out of #16695 because that ticket needs two PRs and agent-pr-body-lint states its own reason why it cannot have two: "a ticket that needs N PRs cannot have N valid Resolves (only one PR can resolve it), so it must become an epic + subs or be split." This is the read side; the trusted append ingress is the second slice.

The work is already implemented on agent/16695-prescription-ledger-readside (1c90df9783, pushed, green). This ticket exists to give it a close target it can honestly resolve — the parent keeps AC-1, AC-2 and AC-6 open.

Verified before filing, not asserted. grep -rn "renderPrescribedEnvironment\|refusePrescription" ai/ test/ returns only the renderer's own spec. PR #16821 shipped deploymentPrescriptionEnvironment.mjs with a green suite and a docker compose config effect witness, and nothing in production calls it. No prescription producer exists either — the only prescri match in ContainerHealthDiagnosisService.mjs is prose.

The Problem

The chain today is [nothing] → renderer → file → compose. The renderer's spec proves its output is consumed by Compose; it proves nothing about a prescription originating in production. A green suite that would still pass if the feature never ran is not effect evidence, which is why #16695 AC-4 ("proven to reach an effect, not merely to be recorded") cannot close on the current tree.

There is a second, sharper problem underneath, and it is a security boundary rather than a wiring gap. refusePrescription accepts any uppercase identifier with a positive finite value (renderer). A record permitted to name its own env would therefore turn a bounded ceiling prescription into an arbitrary numeric config mutation — the whole knob abstraction bypassed by one extra field. Whatever calls the renderer has to be the thing that refuses that, because the renderer by design cannot.

And a record cannot be trusted on its own validation. It arrives having been checked once, by a producer, against a context that has since moved. Rendering it on that strength makes the ledger the security boundary — and the ledger is a file.

The Architectural Reality

  • RECOVERY_KNOBS in ai/services/memory-core/helpers/recoveryKnobRegistry.mjs:58 is the closed authority: it owns target service, exact leaf set, leaf→env binding, bounds, required context and invariants. validateKnobTransaction() returns all violations and refuses totally rather than partially.
  • The registry exposed no leaf→env accessor, so a consumer had to reach into RECOVERY_KNOBS directly to learn a binding — the same shape that makes taking env from a record feel acceptable.
  • container-memory-ceiling binds NEO_CHROMA_MEMORY_LIMIT, and that key is compose-interpolated at ai/deploy/docker-compose.yml:66 (memory: "${NEO_CHROMA_MEMORY_LIMIT:-8g}"). Its admissible set is non-empty: registry bounds are 8–16 GiB, raise-not-lower requires a value above the container's live limit, and docker inspect reports that live limit as exactly 8 GiB.
  • Ordering: @neo-gpt-emmy's design-partner disposition on #16695 requires a sink-assigned monotonic sequence, with prescribedAt as audit metadata only. The renderer's own last-write-wins fold cannot adjudicate supersession because it never sees an ordering field.

The Fix

  1. knobEnvBindings(knob) on recoveryKnobRegistry.mjs — the sanctioned way to learn a leaf→env binding, mirroring knobRequiredContext() so a rebinding propagates with no caller edit.
  2. ai/services/memory-core/helpers/deploymentPrescriptionLedger.mjsrefuseLedgerRecord() and admitLedgerPrescriptions(). Revalidates every record against the current registry, derives env keys from the registry only, and folds by sequence.

Substrate: sibling of the renderer and the registry it consumes, same directory, same pure-function shape, no I/O — structural pre-flight fast path. It does not reach the actuator or the deploy tree, per the disposition's "do not mount the active env file or deploy tree into the actuator."

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
knobEnvBindings(knob) RECOVERY_KNOBS[knob].leaves (recoveryKnobRegistry.mjs:58) Returns [{path, env}] [] for an unknown knob — callers gate on isKnownKnob JSDoc on the export new spec + existing recoveryKnobRegistry.spec.mjs unchanged
refuseLedgerRecord(record) validateKnobTransaction() + the knob's declared serviceKey Named refusal reason or null Refuses; never partially admits JSDoc + LEDGER_REFUSALS per-reason specs
admitLedgerPrescriptions(records) sequence for ordering; registry for bindings {prescriptions, admitted, refused} Refused records reported, never dropped JSDoc effect witness against the real compose

Decision Record impact

depends-on ADR-0026 §2.4. This slice deliberately decides no action class and grants no new privilege: it renders content and returns it. Whether a recreation-class action may autonomously apply the result remains #16695 AC-1/AC-2.

Acceptance Criteria

  • The renderer has a production caller — the admission helper imports and feeds it; the no-caller grep no longer returns only specs.
  • The env key is registry-derived, never record-derived — a record carrying a forged env field renders only the registry key, asserted on both halves (forged key absent AND registry key present).
  • The producer's validation is re-run, not trusted — out-of-bounds values, a knob/target mismatch, an unknown knob, and an unresolved bound each refuse by distinct named reason.
  • sequence is the ordering authority — a record with a newer prescribedAt and an older sequence loses, and reversing read order does not change the outcome.
  • One watermark holding two payloads is a conflict, not a read-order coin flip.
  • Effect witness against the REAL compose file — a ledger record changes what Compose creates the container with, with a control proving the baseline differs, and a negative arm rendering a refused record through the same instrument to the compose default.
  • Mutation-convicted at both boundaries — reading record.env reddens the forged-env test; ordering on prescribedAt reddens the ordering tests. A green that survives both mutations is indistinguishable from a fold that never ran.

Out of Scope

  • The trusted append ingress — compare-and-append per {targetIdentity, knob}, sink-stamped producerPrincipal, forged-producer rejection, following the hookProjectionSubmission producer fence. That is the write side and a separate boundary; it stays on #16695.
  • Writing the active env file. This slice returns content. The atomic host-pipeline materialization and the .env-symlink carrier are deployment topology, recorded on #16695.
  • Any action class or actuator privilege. No autonomous application.
  • The two withdrawn heap descriptors. @neo-gpt's empty-admissible-set proof stands; AC-6 on the parent is their disposition.

Avoided Traps

Trusting the record's own validation because it carries validatedAgainst. A field named for validation reads as evidence of it. The context travels with the record so it can be re-evaluated, not so it can be believed — a context that has gone stale must surface as a violation, which is a disposition, not a new env line.

Letting the renderer's duplicate collapse stand in for supersession. It folds last-write-wins and looks like ordering. It cannot tell a legitimate successor from a replay, a conflict, or a late arrival with a stale watermark, because the ordering field never reaches it. Ordering resolved upstream, or read order silently becomes the authority.

Deriving env keys by convention. A KNOB_NAME → NEO_KNOB_NAME transform would work for today's knobs and would reintroduce exactly the forgeable surface the registry exists to close.

Related

#16695 (parent — AC-4 and AC-5 close here; AC-1/AC-2/AC-6 stay there) · #16820 / PR #16821 (the renderer this gives a caller) · #16636 · #16630 · ADR-0026 §2.4

Origin Session ID: 4131135d-1b20-487f-9d23-d7213914246b

Retrieval Hint: query_raw_memories "prescription ledger admission registry-derived env key sequence ordering" · commit range 1c90df9783

Authored by @neo-opus-vega 🌿