@@ -744,6 +744,97 @@ describe('ApprovalService (node era)', () => {
744744 expect ( req . pending_approvers ) . toEqual ( [ 'position:sales_manager' ] ) ;
745745 } ) ;
746746
747+ // ── the #8710 carve-out, asserted on THIS side (#8863) ──────────────────
748+ //
749+ // Maintainer ruling, 2026-08-15 (#8710, inheriting #8613), verbatim:
750+ //
751+ // > Access-conferring paths filter deactivated positions; addressing
752+ // > paths do not.
753+ //
754+ // Approver routing is an ADDRESSING path, so `expandPositionUsers` reads the
755+ // directory RAW where `PositionGraphService.expandPositionUsers` (sharing,
756+ // access-conferring) filters. Until these two cases the carve-out was held up
757+ // on this side by a doc comment alone: `sharing-rule.test.ts`'s
758+ // `the ADDRESSING primitive stays a RAW directory read` guards the sharing
759+ // helper, not this one, so "fixing" the apparent oversight here left the whole
760+ // suite green. The regression it would ship is a step that routes to NOBODY —
761+ // the expansion comes back short, and when it comes back empty the slot falls
762+ // through to the `position:sales_manager` literal no user can ever act on, i.e.
763+ // the permanently stuck request of #3807 / #3424, surfacing a deployment later.
764+ // Dropping a holder here is fail-OPEN; that is why the lapsed holder stays.
765+
766+ it ( 'position approver: an EXPIRED sys_user_position row STILL routes (ADR-0091 D2 is not applied here)' , async ( ) => {
767+ // ⛔ The load-bearing negative pin. An ablation that adds the ADR-0091 D2
768+ // window to `expandPositionUsers` — the `isGrantActive` filter plus the
769+ // `valid_from`/`valid_until` projection that sharing's `position-graph.ts`
770+ // carries — turns this red on `u5`.
771+ //
772+ // ⚠️ The pin is only worth its line count if the fixture is REALLY expired:
773+ // an assertion that "the expired holder is still in the slate" degrades into
774+ // a tautology the moment the seeded row stops carrying a lapsed window, and
775+ // it degrades SILENTLY (`isGrantActive` reads an absent bound as unbounded,
776+ // so a window-less row survives any filter and the case passes green with
777+ // its own subject ablated — measured on a sibling pin, #9085). The window
778+ // columns below are therefore load-bearing fixture, not decoration, and the
779+ // two guards under them fail loudly if a later edit dulls the blade.
780+ const LAPSED_UNTIL = '2025-06-30T00:00:00Z' ;
781+ const OPEN_UNTIL = '2099-01-01T00:00:00Z' ;
782+ // Past/future of BOTH clocks on purpose — the injected test clock and the
783+ // wall clock — so the pin does not quietly depend on which one a would-be
784+ // filter reads (sharing's reads `Date.now()`; this service has a `clock`).
785+ expect ( Date . parse ( LAPSED_UNTIL ) ) . toBeLessThan ( Math . min ( baseTime , Date . now ( ) ) ) ;
786+ expect ( Date . parse ( OPEN_UNTIL ) ) . toBeGreaterThan ( Math . max ( baseTime , Date . now ( ) ) ) ;
787+
788+ engine . _tables [ 'sys_user_position' ] = [
789+ // Lapsed over a year before the request opens — a filter WOULD drop this.
790+ { id : 'up_lapsed' , user_id : 'u5' , position : 'sales_manager' , organization_id : 't1' ,
791+ valid_from : '2024-01-01T00:00:00Z' , valid_until : LAPSED_UNTIL } ,
792+ // The in-window control: proves an ablation's red is the window filter
793+ // biting, not the fixture failing to seed or the org scope excluding both.
794+ { id : 'up_current' , user_id : 'u6' , position : 'sales_manager' , organization_id : 't1' ,
795+ valid_from : '2024-01-01T00:00:00Z' , valid_until : OPEN_UNTIL } ,
796+ ] ;
797+
798+ const req = await svc . openNodeRequest ( positionInput ( ) , CTX ) ;
799+ // Narrowed off the `autoApproved` discriminant rather than read through the
800+ // union: the neighbours' bare `req.pending_approvers` is what TEST_DEBT's
801+ // frozen TS2339 pile is made of, and a new case may not add to a shrink-only
802+ // ratchet. It reads as an assertion too — an auto-approve outcome here would
803+ // mean the slate came back EMPTY, which is the failure this case is about.
804+ if ( 'autoApproved' in req ) throw new Error ( 'expected a pending request, not an auto-approve outcome' ) ;
805+ // `?.slice().sort()` and not the neighbours' in-place `.sort()`: the field is
806+ // optional on the row, so the bare call is TS18048 under the TEST_DEBT
807+ // re-measure, and an absent slate then reads as `undefined` here — red, which
808+ // is the honest verdict — instead of throwing before any assertion runs.
809+ expect ( req . pending_approvers ?. slice ( ) . sort ( ) ) . toEqual ( [ 'u5' , 'u6' ] ) ;
810+ } ) ;
811+
812+ it ( 'position approver: the routing read never consults the sys_position catalogue' , async ( ) => {
813+ // Limb 3 of the same ruling. `sys_position.active` is gated on the sharing
814+ // side at the RULE EVALUATOR's call site (`positionConfersAccess` in
815+ // `sharing-rule-service.ts`), never inside the graph primitive; the ruling
816+ // gives this path no counterpart gate, so a deactivated position keeps
817+ // routing here too.
818+ //
819+ // Pinned on the READ and not only on the slate, because the slate alone
820+ // cannot fail: nothing here queries `sys_position` at all, so a deactivated
821+ // row is not "kept despite the flag", it is never looked at — and an
822+ // outcome-only assertion would pass for that reason rather than for the
823+ // ruled one. Asserting the absent read is what an ablation adding the
824+ // catalogue lookup + `active` filter turns red.
825+ engine . _tables [ 'sys_position' ] = [
826+ { id : 'pos_sm' , name : 'sales_manager' , organization_id : 't1' , active : false } ,
827+ ] ;
828+ engine . _tables [ 'sys_user_position' ] = [
829+ { id : 'up1' , user_id : 'u5' , position : 'sales_manager' , organization_id : 't1' } ,
830+ ] ;
831+
832+ const req = await svc . openNodeRequest ( positionInput ( ) , CTX ) ;
833+ if ( 'autoApproved' in req ) throw new Error ( 'expected a pending request, not an auto-approve outcome' ) ;
834+ expect ( req . pending_approvers ) . toEqual ( [ 'u5' ] ) ;
835+ expect ( engine . _finds . map ( f => f . object ) ) . not . toContain ( 'sys_position' ) ;
836+ } ) ;
837+
747838 // ── approver expansion: org_membership_level + its deprecated `role` alias
748839 // (ADR-0090 D3) ────────────────────────────────────────────────────────
749840
0 commit comments