You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
security explain: the fls layer's three field-mask states are expressible only in the detail free-text string — no structured per-field slot on ExplainLayer #9329
Found while implementing #9127 (out of its scope — that card's ask was that the layer stop misreporting, which it now does).
#9127 gives the fls layer three states — hidden / partially masked / readable — but ExplainLayerSchema (packages/spec/src/security/explain.zod.ts) has no structured slot for per-field results. The layer carries layer, kernelTier, verdict, detail (a plain string), contributors and the record-grained record. So the three states, and the maskingRule that produced each partial mask, exist only inside the prose of detail:
1 field(s) masked from responses: [ssn]; 2 field(s) PARTIALLY masked — the key is
still served, its value replaced: [phone (phone), bank (bank_account)]
A consumer that wants to know which fields are in which state has to string-parse that sentence.
Why it may matter
ADR-0090 D6 names two uses for the engine, and the second one is machine-facing — the module header states it directly: "the AI-safety story gets its audit substrate — a publish gate can show the SEMANTIC impact of a grant change instead of a JSON diff." A publish gate diffing two explain reports to show that a grant change moved phone from readable to partially masked can only do it by diffing prose today. contributors and record.rules[] exist precisely so the other layers do not have this problem; the field-mask axis has no equivalent.
Why this is filed as an observation, not a defect
Measured on main at e4e5c6e — nothing is broken and nothing is waiting on it:
no in-repo consumer reads the fls layer's detail, or matches on layer === 'fls' outside plugin-security itself (the only hits are the spec enum, the spec's own test, and an unrelated id: 'fls' row in the dogfood authz matrix);
packages/rest's explain route passes the whole decision through untouched;
no publish gate consuming explain reports exists yet.
So this is a speculative capability surface until something actually needs to read it. Per the startup-focus principle, the honest disposition is to wait for a real consumer and let that consumer's needs shape the field, rather than mint a reporting shape now and guess at it — a declared-but-unread structured field would be its own declared ≠ enforced liability on a security surface. Filing it so the reasoning is on record when a consumer does show up.
If one does, the shape worth considering is a per-field array on the layer (field name, state, and the applicable rule for the partial case), which keeps detail as the human rendering rather than the data.
Triage: first-touch grading — closed not planned, reasoning on record (reopen is free; the maintainer can veto).
The card's own measurement is the disposition: nothing is broken (#9127 fixed the misreporting; this is the structured-slot follow-on), there is zero measured pull (no in-repo consumer reads the fls layer's detail or matches layer === 'fls' outside plugin-security; no publish gate consuming explain reports exists), and minting a structured per-field array now would itself be a declared-but-unread key on a security surface — the exact ADR-0049 class the security-object enumeration exists to catch. Startup-scope discipline: let the first real consumer's needs shape the field.
A hold was considered and rejected: the only trigger ("a consumer shows up") is not machine-checkable, and a hold without an executable exit is a state nobody can legally leave. Reopen trigger, for the record: the moment a publish gate (or any surface) actually needs to diff field-mask states across explain reports, reopen this card — the recommended shape (per-field array: name, state, applicable rule for the partial case; detail stays the human rendering) is already written down here and in #9127's trail.
This comment is from the triage seat Routine — session session_01CNcg2rhY7jLn5g5TEbhK4Z.
Activity
os-zhuang commentedon Aug 17, 2026
Triage: first-touch grading — closed not planned, reasoning on record (reopen is free; the maintainer can veto).
The card's own measurement is the disposition: nothing is broken (#9127 fixed the misreporting; this is the structured-slot follow-on), there is zero measured pull (no in-repo consumer reads the
flslayer'sdetailor matcheslayer === 'fls'outside plugin-security; no publish gate consuming explain reports exists), and minting a structured per-field array now would itself be a declared-but-unread key on a security surface — the exact ADR-0049 class the security-object enumeration exists to catch. Startup-scope discipline: let the first real consumer's needs shape the field.A hold was considered and rejected: the only trigger ("a consumer shows up") is not machine-checkable, and a hold without an executable exit is a state nobody can legally leave. Reopen trigger, for the record: the moment a publish gate (or any surface) actually needs to diff field-mask states across explain reports, reopen this card — the recommended shape (per-field array: name, state, applicable rule for the partial case;
detailstays the human rendering) is already written down here and in #9127's trail.This comment is from the triage seat Routine — session
session_01CNcg2rhY7jLn5g5TEbhK4Z.Generated by Claude Code