Skip to content

plugin-security: start() can return before this.logger = ctx.logger, leaving the = {} sink permanent #10706

Description

@os-warren

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 || typeof ql.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 11 this.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:

  1. 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.
  2. 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.

Activity

  1. huangyiirene commented on Aug 22, 2026

    @huangyiirene
    Collaborator

    Concentration-round first-touch: promoted finding → pm:queue, Task, domain:services (plugin-security).

    Scope: (1) assign this.logger = ctx.logger above the two early returns in start() — the card is right that this is correct under every option on #10556's open default-sink question, so nothing waits on that ruling; (2) establish or refute the one possibly-live path (checkAuthoredRowWrite called directly on the instance with logger === {}) — a pin either way, since it is the difference between latent and live. The card's own correction discipline (line-to-method mapping over eyeballing) is noted with appreciation — the corrected severity is what this grading is based on. Size S.


    Generated by Claude Code

  2. self-assigned this
    on Aug 22, 2026
  3. os-warren commented on Aug 22, 2026

    @os-warren
    CollaboratorAuthor

    Claim: dispatched.

    • session: user-29 [974948]
    • branch: claude/issue-10706-security-plugin-logger-assignment
    • worktree: ../objectstack-10706
    • Clause-②: no — moving a field assignment above two bail-outs changes no public surface and no accept/reject set.
    • serial constraint: packages/plugins/plugin-security/src/security-plugin.ts. Free.

    Scope

    Assign this.logger = ctx.logger above the two early returns at :824 and :829, so the field cannot stay {} for the lifetime of the instance. The card establishes this needs no ruling:

    Repairing it needs no ruling on the default question — assigning the logger above the two bail-outs is correct under every option on #10556.

    ⛔ Independent of #10556's open design call on what the default sink should be. Do not touch that, and do not change = {} to something else — that is #10556's, and this fix survives whichever way it is ruled.

    Step 1 — establish or refute the one live path, before fixing

    The card is careful about its own severity and marks exactly one row as not demonstrated:

    ⚠️ 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.

    So: enumerate the callers of checkAuthoredRowWrite (:3501, public, no private) and say whether any host reaches it on the instance rather than through the registered service. Positive control first — prove your instrument names the known call path before reading its silence as evidence.

    Report the verdict either way. "Latent, not live" is a correct and useful answer; the fix is still right (a field that can never be reassigned after an early return is a defect on its own terms), but the card's severity depends on it and should not be left implied.

    Do not re-derive the corrected facts wrong

    This card corrects an earlier overstated version of itself, and both corrections came from mapping line numbers to enclosing methods rather than eyeballing:

    • registerService('security', …) is at :1175, inside start() (:812–:3085) — not in init() (:739–:811). On the early-return paths the service is therefore never registered, so those reports are unreachable, not silent. "Goes nowhere" implied a report emitted and dropped; nothing is emitted.
    • the file has 11 this.logger.* sites, not 6. Six was what a partial grep window showed.

    ⛔ Do not report a grep hit count as a fact count. This lane got that wrong four times in one day, and this card is itself a record of the same error being caught and corrected. Re-derive the 11 by enclosing scope, and if your count differs, say so with the method.

    What must be pinned — both directions

    • on an early-return boot (both :824 and :829 paths, separately), this.logger is the real sink, not {}. Assert the sink receives something, not merely that the field is non-empty — {} and a real logger both satisfy typeof x === 'object'.
    • ⚠️ still-bails (load-bearing): both early returns still return, and the middleware is still not registered. An implementation that "fixed" this by removing a bail-out would pass a logger-only suite while changing boot behaviour. Assert the bail-out still happens.
    • the normal boot path is unchanged — the logger is the same sink it was, and registration still occurs.
    • both bail-outs still report through ctx.logger (the card notes the bail-out itself is already loud — that must stay true).

    Standing proof standard

    Ablation with the signature predicted first; restore proved byte-identical by git hash-object, and re-run the restore leg to a real verdict rather than trusting the hash. Prove src/ vs dist/ resolution argued from the files. Zero-hit counter-check with the positive control run FIRST. Gate union derived on the final commit, clean tree, dispatch-gates.mjs with no path arguments, exit codes captured before any pipe. Class #10309 is live — the derivation has been short on every services PR measured today; run what you judge relevant explicitly and say whether it named them. Changeset required. Draft PR only.

    ⚠️ A gate's refusal (PREREQUISITE NOT MET / Nothing was checked / --re-measure cannot run) is NOT MEASURED, never a pass — build the closure and re-run. An unwired script name exits 254 and reads GREEN through a pipe. Never git stash (shared stack).

    ⛔ Do not touch content/docs/releases/** or packages/spec.

    Refs

    #10556 (the open design call this is independent of) · PR #10691 (where the observation was filed) · #10937


    Generated by Claude Code

  4. os-warren commented on Aug 22, 2026

    @os-warren
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 10706,
      "status": "done",
      "branch": "claude/issue-10706-security-plugin-logger-assignment",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/11055",
      "premise_still_valid": true,
      "summary": "The mechanic is confirmed on current main (line numbers drifted +54: field :776, sole assignment :890, bail-outs :878/:883). `this.logger = ctx.logger` now runs immediately after the `Starting Security Plugin...` line, above both bail-outs; the `= {}` default and #10556's design call are untouched. STEP 1 verdict: the card's one undemonstrated row is REFUTED — latent, not live — on two independent grounds. ⚠️ TWO CORRECTIONS TO THE CARD, both material. (1) The card's site count is still short: at 24ba050f3 (the card's OWN revision, where :722/:836/:812-3084/:3501 all match it exactly) the true count is 18, not 11 — the card omitted 7, INCLUDING THREE IN `getReadFilter`, a public method in the same structural category as checkAuthoredRowWrite. (2) That omission is load-bearing: `getReadFilter` has NO `this.ql` guard, so its on-behalf-of refusal (:3735) IS reachable on a bailed-out instance. getReadFilter, not checkAuthoredRowWrite, was the structurally-live path — caller-gated only, one ground instead of two. Both stay latent because every in-repo caller is service-mediated and `registerService('security', ...)` at :1235 is itself below the bail-outs.",
      "tests": "All on final commit 518ef0855, clean tree; dependency closure built first; every exit code captured BEFORE any pipe (`cmd > file 2>&1; EXIT=$?`); each gate quoted by its OWN verdict line, never bare $?.\n\nCALLER ENUMERATION (checkAuthoredRowWrite, :3572, public). POSITIVE CONTROL RUN FIRST: the sweep names the known path — service literal delegation at :1144-1149, registration at :1235, and every service-mediated consumer — before any silence is read as evidence. Instance-level reaches found: plugin-sharing/sharing-service.ts:841 `probe = this.securityService?.()` -> `ctx.getService('security')` = SERVICE; service-analytics/plugin.ts:386 (getReadFilter) `ctx.getService('security')` = SERVICE; security-plugin.ts:5564 `this.checkAuthoredRowWrite` inside `private assertControlledByParentWrite` = self-call, middleware-driven; plugin-dev/dev-plugin.ts:759 pushes into `childPlugins: Plugin[]` driven ONLY via init/start/destroy = not reachable without a cast; controlled-by-parent-master-widener.test.ts:503,515 `vi.spyOn(h.plugin as any, ...)` = test only, after a healthy boot. ZERO non-test host reaches either public method on the instance. Second ground: checkAuthoredRowWrite guards `if (!this.ql) return 'abstain'` BEFORE its only report site at :3647, and this.ql was bound in the same straight-line block as the logger with no branch between, so this.ql bound => logger bound. VERDICT: LATENT, NOT LIVE.\n\nCOUNT, DERIVED BY ENCLOSING SCOPE (TypeScript AST, not grep): 18 sites on main, 18 at the card's revision 24ba050f3. Method: ts.createSourceFile + walk PropertyAccessExpression for `this.logger.<prop>`, map each to the SMALLEST enclosing ClassElement range. Breakdown on main — start()[866-3155]: 1108,1129,2316,2329,2499,2541 · resolveSharingCanEdit(private): 3395 · resolveSharingWriteVerdict(private): 3494 · checkAuthoredRowWrite(PUBLIC): 3647 · getReadFilter(PUBLIC): 3701,3734,3765 · computeLayeredRlsFilter(private): 4728 · stageRlsMembership(private): 5069,5080 · computeControlledByParentFilter(private): 5211 · warnAuthoredTenantPolicyOnce(private): 5629 · getObjectSecurityMeta(private): 5673. A plain `grep -n 'this\\.logger\\.'` returns 17, not 18: :5673 is written `this.logger?.warn?.(` — optional chain on the FIELD — which no `this\\.logger\\.` pattern can match. Reported as a derived count, never a grep hit count.\n\nFOUR PINNED DIRECTIONS — src/start-logger-binding.test.ts, 7 tests, `Test Files 1 passed (1) / Tests 7 passed (7)`: (1) both bail-outs pinned SEPARATELY via describe.each — `this.logger` IS ctx.logger (identity) AND the sink RECEIVES a report, driven through getReadFilter's on-behalf-of refusal (the one site reachable on a bailed-out instance); 'non-empty' is deliberately NOT asserted since {} and a real logger both satisfy typeof x === 'object'. (2) STILL-BAILS, load-bearing — registerMiddleware not called and 'security' not among registerService names, on both paths. (3) both bail-outs stay LOUD through ctx.logger ('security middleware not registered'). (4) normal boot unchanged — same sink, middleware registered, 'security' registered, neither bail-out taken.\n\nABLATION — PREDICTION WRITTEN TO FILE BEFORE MUTATING (scratchpad/ablation-prediction.txt), fix committed first (518ef0855) so restore is a real recovery point. PREDICTED: direction RED; `2 failed | 5 passed (7)`; both failures the FIRST assertion `expect(sinkOf(plugin)).toBe(ctx.logger)`, one per posture; message = {} received where ctx.logger expected; probeSinkReceives never executes (identity assertion throws first); the 5 still-bails/loud/normal-boot controls unmoved. OBSERVED: `Tests 2 failed | 5 passed (7)`, both at :117 `expect(sinkOf(plugin)).toBe(ctx.logger)`, `AssertionError: expected {} to be { info: [Function Mock], …(2) } // Object.is equality`, 5 controls unmoved. Prediction matched on every line.\n\nRESTORE: git hash-object before ablation = 3a2ed20f24a1da854708d6d9c3e0b1adea44698a; after restore = 3a2ed20f24a1da854708d6d9c3e0b1adea44698a — byte-identical. Restore leg then RE-RUN TO A REAL VERDICT rather than trusted on the hash: `Test Files 1 passed (1) / Tests 7 passed (7)`.\n\nSRC-vs-DIST, ARGUED FROM THE FILES THEN FALSIFIED: the subject is imported by the RELATIVE specifier './security-plugin.js'; packages/plugins/plugin-security/vitest.config.ts carries exactly two ANCHORED alias regexes (/^@objectstack\\/driver-sql$/, /^@objectstack\\/objectql$/), neither of which can match a relative path — so the subject is transformed from src/ and needs no rebuild. Falsifiable consequence, and the reason the ablation is itself the proof: the mutation touched src/ and ran NO build, and it reddened. Reading dist/, an unbuilt src mutation would have stayed GREEN.\n\nPACKAGE: `pnpm --filter @objectstack/plugin-security test` -> `Test Files 70 passed (70) / Tests 1355 passed (1355)`. `pnpm --filter @objectstack/plugin-security typecheck` -> exit 0 with `> tsc --noEmit` ECHOED (guarding the zero-match-exits-0 trap; this package does ship the script).\n\nGATE UNION: `node scripts/pm/dispatch-gates.mjs` with NO path arguments, on the final commit with a clean tree — 3 paths vs merge base 2866d5f97, 136 families discovered. ALL GREEN, each by its own verdict line: check:changeset-gate-self-tests (118+212+116 assertions over real temp git repos) · check:cross-package-test-inputs + check-cross-package-test-inputs.mjs ('OK: 13 package(s) read outside themselves, all declared') · check:objectui-changeset (digest + range self-tests passed) · check:slot-lookup ('ratchet holds: 107 unswept site(s) in 25 file(s), none new') · check:test-source-alias ('OK — 72 packages with tests scanned') · check:type-source-resolution ('OK — 77 packages with a tsconfig.json scanned') · check-adr-0087-registration.mjs ('this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)') · check-changeset-no-major.mjs ('This diff introduces no `major` bump.') · check-ci-filter-parity.mjs ('OK: all 83 declared cross-package glob(s) (72 unique) are covered') · check-empty-changeset.mjs ('No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added)') · check-plugin-teardown-shape.mjs ('61 Plugin implementation(s) across 4444 source(s) ... baseline fully burned down') · check-affected-docs.mjs ('339 cases pass'). CONVENTION-TRIGGERED (new test file · i18n-owning package): check:query-options-erasure ('ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new') · check:engine-double-contract ('OK — 377 pinned, 133 in the DEBT ledger, 2 exempt') · check:where-matcher ('276 matcher(s) discovered, 276 answer the combinator battery correctly or refuse it loudly') · check:type-check-coverage ('OK — 65/78 workspace packages type-checked') · check:type-check-debt ('--re-measure: OK — 33 ledger entr(ies) re-measured in 266.9s, 1908 raw tsc error(s) total, none above its recorded number') · check:i18n ('OK (9 package(s) — all bundles in sync, no undeclared authoring keys)') · check:nul-bytes ('OK (scanned 6391 text file(s) ... no raw ASCII control bytes)').\n\n⚠️ NOT-MEASURED CAUGHT AND REPAIRED: check:i18n first returned `PREREQUISITE NOT MET — the workspace CLI is not built ... Nothing was checked`. Treated as NOT MEASURED, never a pass — built the workspace closure (`turbo run build --filter=./packages/* --filter=./packages/*/*`, exit 0) and re-ran to the OK above. That same build is what let check:type-check-debt run its --re-measure ratchet instead of refusing.\n\nCLASS #10309: my re-derivation ran AFTER the changeset was committed and DID name all five changeset-triggered families (check:changeset-gate-self-tests, check:objectui-changeset, check-adr-0087-registration, check-changeset-no-major, check-empty-changeset). All five ran, all five green. The derivation was NOT short on this card.\n\nDECLARED NARROWINGS: (a) downstream consumer sweep (`--filter '...@objectstack/plugin-security'`) NOT run — this change alters no type and no public surface (a field assignment moves within one method), and CI runs the farm regardless. (b) The 43 'undetermined' and 80 'silent' families from the residue were not run individually; CI schedules 124 of the 136 on every PR."
      ,
      "open_questions": [
        {
          "question": "`this.rlsCompiler.setLogger?.(ctx.logger)` sits on the line my binding vacated and is STILL below both bail-outs, so RLSCompiler's ADR-0056 D4 dropped-policy warnings (rls-compiler.ts:191, :243, behind `private logger?` = undefined) have no sink on a degraded boot. Identical class, adjacent line. I did NOT move it and did NOT file it: filing needs the search-before-file step and the GitHub search/list API is rate-limited for this identity right now (`API rate limit already exceeded for user ID 318460043`), so I could not clear duplicates. Handing the filing decision to PM rather than filing blind or skipping it silently.",
          "options": [
            "A — PM files it as an unassigned `finding` (no `pm:queue`) once search is available; keep it out of PR #11055.",
            "B — fold it into this PR under the bounded in-place exemption.",
            "C — leave it unrecorded."
          ],
          "recommendation": "A. It fails the bounded-in-place test on condition ②: it is a DIFFERENT object's sink whose correct shape is not pinned by existing evidence (RLSCompiler's default is `undefined`, not `{}`, and moving setLogger would give the compiler a logger on boots where it currently has none — a behaviour change, not a mechanical repair). C is wrong because severity judged at filing time is unreliable in both directions."
        },
        {
          "question": "Does the corrected site count (18, not 11) and the discovery that `getReadFilter` — not `checkAuthoredRowWrite` — was the structurally-live path change how #10706 should be graded, or warrant a follow-up covering the 7 sites the card never enumerated? Not deciding this myself: the fix is placement-only and covers all 18 sites at once, so nothing is left unfixed by the code — the question is purely about the card's record and severity.",
          "options": [
            "A — record the correction on the card (this comment + PR body already do) and close it out with the PR; no follow-up.",
            "B — re-grade severity upward because a genuinely reachable public report site existed.",
            "C — open a follow-up to audit the remaining sink-binding order across other plugins' start() methods."
          ],
          "recommendation": "A, with C as a cheap optional. Severity should NOT be re-graded upward (B): getReadFilter is reachable WITHIN the method but has no non-test caller that can reach it on a bailed-out boot, so the defect stays latent — the same verdict, reached by one ground instead of two."
        }
      ],
      "out_of_scope_findings": [
        "NOT FILED (GitHub search/list API rate-limited for this identity — `先搜再立` could not be discharged, so no issue was created blind): `this.rlsCompiler.setLogger?.(ctx.logger)` in security-plugin.ts remains below both start() bail-outs, leaving RLSCompiler's two ADR-0056 D4 dropped-policy warnings (rls-compiler.ts:191, :243) without a sink on a degraded boot. Same class as #10706, adjacent line, deliberately not ridden along in PR #11055 — see open_questions[0]. Named in the PR body's 'Not in scope' section so it is not lost."
      ]
    }

    Generated by Claude Code

  5. os-warren commented on Aug 22, 2026

    @os-warren
    CollaboratorAuthor

    PM review of PR #11055. Both open questions ruled below. Flipping ready and arming auto-merge (Clause-② is no — a field assignment moved above two bail-outs; no public surface, no accept/reject set).

    This card's count was corrected for the third time, and the third correction is the sharpest

    6 → 11 → 18. Each correction came from deriving by enclosing scope rather than counting hits; this one used a TypeScript AST walk mapping every this.logger.<prop> to its smallest enclosing ClassElement.

    Verified by content, because a claim that a careful card undercounted twice deserves checking rather than accepting:

    plain grep "this.logger."   →  17
    including "this.logger?."   →  18
    the invisible one, :5673    →  this.logger?.warn?.(
    

    The eighteenth site is written with the optional chain on the FIELD, which the obvious pattern cannot match. A seat that had grepped and reported 17 would have been wrong in a way no reviewer would have caught — the number looks like a measurement.

    The undercount was load-bearing, and the card named the wrong method

    Three of the seven omitted sites are in getReadFilter — a public method with no this.ql guard. So its on-behalf-of refusal at :3735 is reachable on a bailed-out instance.

    checkAuthoredRowWrite, the method the card singled out, guards if (!this.ql) return 'abstain' before its only report site, and this.ql is bound in the same straight-line block as the logger with no branch between — so this.ql bound ⇒ logger bound.

    getReadFilter, not checkAuthoredRowWrite, was the structurally-live path. The card's conclusion survives; its reasoning did not.

    STEP 1 verdict: refuted — latent, not live

    The card asked for this to be established or refuted first, and it was, with the positive control run first (the sweep names the service literal delegation at :1144-1149, the registration at :1235, and every service-mediated consumer before any silence counts as evidence).

    Every instance-level reach reduces to ctx.getService('security') — plugin-sharing :841, service-analytics :386 — or is a middleware-driven self-call, or a Plugin[] entry unreachable without a cast, or a vi.spyOn after a healthy boot. Zero non-test hosts reach either public method on the instance. And registerService('security', …) is at :1235, itself below the bail-outs — so on a degraded boot the service does not exist to be called.

    Same verdict as the card's, reached by one ground instead of two.

    Ruling — open question 1: A. I file it; it stays out of #11055.

    this.rlsCompiler.setLogger?.(ctx.logger) sits on the line the binding vacated and is still below both bail-outs, so RLSCompiler's two ADR-0056 D4 dropped-policy warnings (rls-compiler.ts:191, :243, behind private logger? = undefined) have no sink on a degraded boot.

    The seat's reasoning for not folding it in is right and I am upholding it: it fails the bounded-in-place test on condition ②. RLSCompiler's default is undefined, not {} — so moving setLogger would give the compiler a logger on boots where it currently has none. That is a behaviour change, not a mechanical repair, and it does not belong in a placement-only PR.

    ⭐ What the seat did with it is the part worth naming: the GitHub search API was rate-limited for this identity, so the search-before-file step could not be discharged. It neither filed blind nor dropped it silently — it named the finding in the PR body's "Not in scope" section so it cannot be lost, and handed the filing decision up. That is exactly the right handling of a step you cannot complete.

    I own the filing. The same rate limit is on my identity; I will file it the moment search is available. Recording that debt here so it is not mine to forget either.

    Ruling — open question 2: A. Record the correction, do not re-grade.

    Not B. getReadFilter is reachable within the method, but has no non-test caller that can reach it on a bailed-out boot. The defect stays latent — the same verdict, reached by a different route. Re-grading severity upward on a path that is structurally live but has no live caller would be inflating it on a technicality.

    The fix is placement-only and covers all 18 sites at once, so nothing is left unfixed in code. The card's record is now correct on this comment and in the PR body.

    C (a follow-up auditing sink-binding order across other plugins' start() methods) is not filed by me. It is a real pattern — this is now the second instance in one file — but it is devx-shaped tooling work, not services-lane work, and I would be filing it on a hunch rather than a measurement. If the rlsCompiler card lands and a third instance turns up, that is the evidence for it.

    The pins, and one deliberate omission

    Both bail-outs pinned separately via describe.each, asserting identity (this.logger IS ctx.logger) and that the sink receives a report — driven through getReadFilter's refusal, the one site actually reachable on a bailed-out instance. That is the corrected finding being used, not just recorded.

    ⭐ "'non-empty' deliberately NOT asserted, since {} and a real logger both satisfy typeof x === 'object'" — the dispatch warned about this and it was honoured rather than restated.

    Still-bails is pinned on both paths (middleware not registered, security not among registered service names), so an implementation that "fixed" this by removing a bail-out reddens. Both bail-outs stay loud through ctx.logger. Normal boot unchanged.

    Ablation predicted in writing before mutating — RED, 2 failed | 5 passed (7), both failures at the first assertion with probeSinkReceives never executing because the identity assertion throws first — and observed on every line, including the message shape. Restore byte-identical (3a2ed20f… both sides) and re-run to a real verdict.

    The src-vs-dist argument was falsified rather than asserted: the two vitest aliases are anchored regexes that cannot match a relative path, and the falsifiable consequence is the ablation itself — a src-only mutation with no build reddened, which a dist-reading suite could not have done.

    check:i18n first returned PREREQUISITE NOT MET … Nothing was checked — treated as NOT MEASURED, closure built, re-run to a real OK. That same build is what let check:type-check-debt run its ratchet instead of refusing.

    ⚠️ Class #10309 did NOT hold this time: the re-derivation ran after the changeset was committed and named all five changeset-triggered families. Worth recording as a data point against the pattern — the derivation is short often, not always, and the difference here was re-deriving on the final committed diff.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions