ADR-0079 A frame carries properties, never identities, and two of the three symptoms were not a gap
#Context
Three records this phase stopped at the same sentence. ADR-0075 left live-feed's subtree-demand permission declared, demonstrated and unenforced, *"because the engine reads a frame and has no way to know which component a Scope is."* ADR-0077 said the same about popover. ADR-0078 said it a third time about a nested Scope's ground.
Three symptoms of one thing is an argument for one record about it, and the obvious shape of that record is *put more in the frame*. That is the wrong answer and this record is mostly about why.
#The principle: a frame says what a Scope is, never which component it is
ADR-0021 made a claim about this system's enforcement surface and it has held for thirty-six components:
The generated pairing matrix is keyed on layers, levels, themes, densities, motion classes and easings — never on component names — so it stands at 194 cells across 10 dimensions today and stays there at forty-five components.
It stands at 194 cells at thirty-six built. A frame that named its components would end that, and it would end it in the direction the whole system is arranged against: the engine would begin to know things about live-feed that it does not know about a Scope, and every check downstream would acquire a component-shaped special case.
So the question *"how do we tell the engine which component this is"* is refused, and the one that replaces it is: is the thing it needs a property? Asked of the three symptoms, that question answers them differently, and two of the three turn out not to have been a gap at all.
#The ground: already in the frame, and predicted from the wrong place
ADR-0016 has every base and raised context predict the theme substrate. That was right while every Scope sat on the substrate, and ADR-0078's probe found what it costs when one does not: a raised Scope inside a Region predicted 0.067205 and the pixels showed 0.099587, the Region's own surface-raised. The declaration was honest and the prediction was wrong, and the scene was arranged around the failure rather than through it.
The prediction is re-based, and the rule generalises rather than gaining an exception. A base or raised Scope predicts the ground of whatever Scope contains it, and for a top-level Scope that is the theme substrate — which is every Scope built, so nothing that existed moved.
The parent's ground is in the frame already. It is the background that element paints, read off the parent Scope element. No attribute was added and none was needed: what the engine could not do was not read a value, it was ask the right element for one.
And the check stays honest rather than becoming vacuous, which is the part that had to be got right. The prediction reads the *parent Scope element's* own background; floorRendered walks up from *this* Scope's parent element and finds whatever is nearest. A full-bleed layer smuggled between a child and its parent Scope is picked up by the second and not by the first, so ADR-0016's laundering case is still caught — one level in, on a component that could not previously exist.
An overlay is not re-based. It grounds on the scrim over what it covers, and what it covers is the view rather than one Scope. modal is unaffected and the branch says so.
#The demand permission: a property, and the one thing that does need an attribute
LIVE_FEED_MAY_HOLD_A_DEMAND and PAGE_HEADER_MAY_HOLD_A_DEMAND are opposite answers to a question ADR-0053 tabulated, and neither is enforced. Put to the question above, this one is a property — whether this Scope's subtree may hold the View's demand is a fact about the Scope, in the way its stacking context and its density are, and it is not derivable from anything the frame carries: region and live-feed differ on it while agreeing on layer, level, stacking and density.
So it earns an attribute, and it is specified here rather than written. A Scope serializes whether its subtree may hold a demand; the engine already computes per-Scope demands and would refuse one inside a Scope that says it may not. That is one entry in core's attribute table, one line in each of the eight Scope components, and a rule in the engine — and it is a change to the serialization surface, which every fixture in the repository is written against.
Naming it is not owing it. It is the next unit on this row and it is specified to the attribute.
#Rejected options
Put the component name in the frame. The direct answer to the sentence three records stopped at, and the reason this record exists. Rejected on ADR-0021's own claim: the enforcement surface is keyed on properties and stands at 194 cells across thirty-six components because of it. A frame that named components would let every check downstream acquire a component-shaped special case, and the first one would look reasonable.
Put the parent's surface in the frame as an attribute. It would have worked and it would have been one line. Rejected because it is a value the frame already carries — the parent element's own background — and an attribute restating a rendered fact is a second copy that can disagree with the first. The engine was asking the wrong element, not missing a datum.
Re-base the prediction to the pixels behind the Scope rather than to the parent Scope's background. Simpler, and it would make every nested Scope pass. Rejected because it makes the check vacuous exactly where it matters: predicted and rendered would be the same walk, and a smuggled full-bleed layer would satisfy both.
Leave the ground alone and let popover render on the substrate. Available, since page-header grounds on surface-page and ADR-0078's probe used it. Rejected because it decides where a popover may be anchored by what the measurement can express, which is the tail wagging the dog — a popover is anchored to a control, and controls live inside regions.
#Consequences
Sixty scenes unchanged to six decimals. The re-basing is inert on everything built, because every Scope built is top-level.
Scene 63 is the composition it wanted in the first place. ADR-0078 nested its probe in a PageHeader to avoid this failure and recorded the reason; the scene now nests in a Region, and the value that used to be the failure is the prediction: predicted 0.099587, rendered 0.099587. Containment is unchanged and still exact — outer nested 69,372, inner governed 69,372.
popover is buildable. Both of ADR-0077's obstacles are gone: containment by ADR-0078, the ground by this record. What remains on that row is the component.
One of the three symptoms survives, and it is smaller than it looked. Subtree demand permissions need one attribute, specified above. The other two were the engine asking the wrong element and a rule written before nesting existed.
No token, no component, no check kind. One prediction re-based, one probe re-nested, two baselines.
#Measured
pnpm budgetafter the change: 60 scenes, every one identical to six decimals *(darwin-arm64, this pass)*.- Scene 63, re-nested in a
Region:#scope-innerpredicted 0.099587, rendered 0.099587, suspicious false;#scope-outernested 69,372,#scope-innergoverned 69,372 *(darwin-arm64, this pass)*. - The same scene with the re-basing removed:
substrate floorfailure on#scope-inner, exit 1. Restored, exit 0 *(darwin-arm64, this pass)*. - Themed siblings —
field-day0.10771 / 0.14320,interior-dark0.08470 / 0.15292,interior-light0.06506 / 0.15134. - The pairing matrix: 194 cells across 10 dimensions at 36 built components, the figure ADR-0021 predicted would not move with the roster.
pnpm battery— the verdict this pass is measured by.