Observation filed while landing PR #10691 (the #10556 optional-error-sink paydown). Not a claim; unassigned. ⛔ Deliberately not fixed there, and it is independent of the open design call on #10556 about what that default sink should be — it survives whichever way that is ruled.
⚠️This card corrects an earlier, overstated version of the same observation that I reported on #10556. The correction is in the last section, and the corrected severity is materially lower. Recording it here so the wrong version does not outlive the right one.
The mechanic
packages/plugins/plugin-security/src/security-plugin.ts:
- :722 —
private logger: { info?; warn?; error? } = {} — the field is an empty object from construction. - :836 —
this.logger = ctx.logger — the only assignment, inside start(). - :824 and :829 — two
return; statements in start() that sit above :836:
try{ql=ctx.getService<IObjectQLEngine>('objectql');metadata=ctx.getService<IMetadataService>('metadata');}catch(e){ctx.logger.warn('ObjectQL or metadata service not available, security middleware not registered');return;// :824 — :836 never runs}if(!ql||typeofql.registerMiddleware!=='function'){ctx.logger.warn('ObjectQL engine does not support middleware, security middleware not registered');return;// :829 — :836 never runs}On either path this.logger stays {}for the lifetime of the plugin instance. The field is never reassigned anywhere else (grep -n 'this.logger = ' → one hit).
Note both bail-outs report through ctx.logger, which is a real sink — so the bail-out itself is loud. What is left holding {} is the plugin's own field.
Reachability — the part that needs care
The file has 11this.logger.* report sites. They split into three groups, and only the third is a live concern:
| sites | enclosing scope | reachable with logger === {}? |
|---|
| :1048, :1069, :2245, :2258, :2428, :2470 | closures created inside start(), registered at :1175 and on the engine middleware — all after :836 | No. On an early-return boot they are never created, because registration is also after :836. |
| :4594, :4935, :4946, :5495 | private helpers driven by the engine middleware | No, same reason — the middleware is not registered on those paths. |
| :3576 | checkAuthoredRowWrite, a public instance method (:3501, no private) | Possibly. It does not depend on registration. A host holding the SecurityPlugin instance can call it directly, and on an early-return boot — or between init() and start() — its this.logger.warn?.() goes nowhere. |
⚠️ The third row is not demonstrated, only shown to be structurally possible: no in-repo caller invokes checkAuthoredRowWrite on the instance rather than through the registered security service. Whoever picks this up should establish or refute that first — it is the difference between a latent hazard and a live defect.
Why it is worth a card even at the lower severity
The value of = {} is that it makes "no sink" unrepresentable as a state you can notice: the field always looks assignable and every report site is written this.logger.warn?.(…), so no code path can tell a configured sink from the empty default. The two early returns are what turn a boot-ordering detail into a permanent one. Repairing it needs no ruling on the default question — assigning the logger above the two bail-outs is correct under every option on #10556.
The correction
I first reported this as: "the security service registered back in init() keeps serving, so its six fail-closed reports — including hasWriteBypass failed … denying and the ADR-0123 tenant-wall refusal — go nowhere at all."
That was wrong on two counts, and both were found by mapping line numbers to enclosing methods instead of eyeballing the file:
registerService('security', …) is at :1175, inside start() (which spans :812–:3085), not in init() (:739–:811). On the early-return paths the service is therefore never registered at all — so those reports are unreachable, not silent. "Goes nowhere" implied a report that is emitted and dropped; nothing is emitted.- The count was six; the file has eleven
this.logger.* sites. Six was what a partial grep window showed.
What survives the correction is the mechanic in the first section and the single public-method path above.
Observation filed while landing PR #10691 (the #10556 optional-error-sink paydown). Not a claim; unassigned. ⛔ Deliberately not fixed there, and it is independent of the open design call on #10556 about what that default sink should be — it survives whichever way that is ruled.
The mechanic
packages/plugins/plugin-security/src/security-plugin.ts:private logger: { info?; warn?; error? } = {}— the field is an empty object from construction.this.logger = ctx.logger— the only assignment, insidestart().return;statements instart()that sit above :836:On either path
this.loggerstays{}for the lifetime of the plugin instance. The field is never reassigned anywhere else (grep -n 'this.logger = '→ one hit).Note both bail-outs report through
ctx.logger, which is a real sink — so the bail-out itself is loud. What is left holding{}is the plugin's own field.Reachability — the part that needs care
The file has 11
this.logger.*report sites. They split into three groups, and only the third is a live concern:logger === {}?start(), registered at :1175 and on the engine middleware — all after :836privatehelpers driven by the engine middlewarecheckAuthoredRowWrite, a public instance method (:3501, noprivate)SecurityPlugininstance can call it directly, and on an early-return boot — or betweeninit()andstart()— itsthis.logger.warn?.()goes nowhere.checkAuthoredRowWriteon the instance rather than through the registeredsecurityservice. Whoever picks this up should establish or refute that first — it is the difference between a latent hazard and a live defect.Why it is worth a card even at the lower severity
The value of
= {}is that it makes "no sink" unrepresentable as a state you can notice: the field always looks assignable and every report site is writtenthis.logger.warn?.(…), so no code path can tell a configured sink from the empty default. The two early returns are what turn a boot-ordering detail into a permanent one. Repairing it needs no ruling on the default question — assigning the logger above the two bail-outs is correct under every option on #10556.The correction
I first reported this as: "the
securityservice registered back ininit()keeps serving, so its six fail-closed reports — includinghasWriteBypass failed … denyingand the ADR-0123 tenant-wall refusal — go nowhere at all."That was wrong on two counts, and both were found by mapping line numbers to enclosing methods instead of eyeballing the file:
registerService('security', …)is at :1175, insidestart()(which spans :812–:3085), not ininit()(:739–:811). On the early-return paths the service is therefore never registered at all — so those reports are unreachable, not silent. "Goes nowhere" implied a report that is emitted and dropped; nothing is emitted.this.logger.*sites. Six was what a partial grep window showed.What survives the correction is the mechanic in the first section and the single public-method path above.