Skip to content

[maintainer] refactor(plugin-security): split src/security-plugin.ts (10,746 lines, +2,803 in 30 days) along its SecurityPlugin seams, one move-only slice per PR #25223

Open

Description

@objectstack-fleet

Filing gate ③: a task the maintainer dispatched, quoted verbatim below. Filed by domain:cli seat 1 (seat post #6024) · os-sales · session session_01CxBKycaiUpA1yGx1XyoGqX, as the restart act of #24586. ⛔ Not a claim.

The maintainer's words, verbatim (#24586, recorded in 6270097840): 「24586 同意A,是否建议这波分拆等 v18 发布之后再处理。」 The restart condition (npm @objectstack/spec latest ≥ 18.0.0) was read true by triage in 6291735609. The ruling's restart act: "one [maintainer] card per file over 10,000 lines into its owning lane … Each card starts with a fresh seam measurement taken at that time, in #23050's shape." This file was under the line at the ruling (7,943 lines 30 days ago) and is over it now, so the ruling's per-file rule covers it.

Reader who acts: the domain:services seat (packages/plugins/plugin-security is that lane's), one dispatch per slice.

Measured (on origin/main 123ff6f4e, 2026-10-22)

History note: this clone's history starts 2026-09-17, so "total" commit counts are lower bounds; the 7- and 30-day windows are complete, and git blame --since=2026-09-22 agrees with a diff against the last pre-window commit fa2980341 to within 7 lines.

  • Size and churn: 10,746 lines (7,943 thirty days ago: +2,803, +35.3%). 20 commits in 7 days, 98 in 30 days. git blame --since=2026-09-22 attributes 4,847 lines (45.1%) to the last 30 days.
  • Structure: a 1,247-line module preamble, then one SecurityPlugin class (9,499 lines, 124 members). start() alone is 3,197 lines: setup and service registration, the main ql.registerMiddleware authz middleware (2,203 lines), a second middleware, bootstrap and the org-admin auto-grant. The largest sections:
section lines lines from the last 30 days
start(): main ql authz middleware 2,203 918
controlled-by-parent (resolveCbpRelation, computeControlledByParentFilter, assert*ControlledByParent*) 1,541 774
module preamble 1,247 571
read filters + field masks + object admission (getReadFilter, canReadObject, canWriteObject, …) 1,083 795
addressed writes + RLS filter layers + tenant wall 1,075 309
baselines + explainAccessForCaller + sharing verdicts 936 470
policy collection, object security meta, outage probes 719 259
start(): setup + service registration 623 229

The ask (from #24586's ruling and #23050's shape)

  1. Split along the existing seams, one area per PR. Each slice is move-only: no logic changes.
  2. Shared state stays explicit: moved code reaches the class's shared members through one named context object. ⛔ Not a second copy of a helper, ⛔ not a re-derivation.
  3. Every slice proves it moved nothing: every gate, baseline, ADR anchor and docs anchor that reads the file by path is migrated in the same PR and stays green. ⛔ A gate whose target moved must not go silently empty.
  4. Serial with the file's in-flight PRs (a slice waits for an open PR on the same region, or the PR merges main after the slice).
  5. ⛔ No new gate.

Not in this card

Any behaviour change. A design change to the shared members beyond giving them an explicit context. Splitting files in other lanes (#24586 hands those to their own seats).

Dedupe

MCP search_issues on this repository, closed included: "split large file move-only service-automation engine.ts security-plugin.ts auth-manager.ts decompose modules" → 2 hits: #24586 (the ruling this card carries out) and #23050 (closed, rest-server.ts). New.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Serial note · domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · 2026-10-22T13:29Z. ⛔ Not a claim. Thread-read: none

    packages/plugins/plugin-security/src/security-plugin.ts is changed by open PRs #25215 and #25198 (#24932, with the maintainer), and #25095 (p2, security, queued ahead of this card) lands in its detail write gate. Each move-only slice of this card is cut after those land or are withdrawn, on a fresh seam reading of the merged file.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p2 · series card, cut 1 of the security-plugin.ts split: the class's leaf readers (RLS policy collection, unresolved-posture probes, field-level masks, field-name tables) move out, and the seam plan for the series is set · 2026-10-22T19:41Z
    Session: session_017BoLT8Gaa8t9a7AWVH8qZa
    Account: os-sales (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-3fzya4 (the harness-pinned branch for this session; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record. Each later cut restarts this same branch from main after the previous cut merges.)
    Worktree: objectstack-issue-25223
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    Ruling-ref: 6348891160 (the series' .d.ts bar, item 1, applied directly as its item 5 orders)

    Prerequisites, read in this act: #25215, #25198 and #25256 (#25095) are all MERGED; none of the 13 open PRs touches packages/plugins/plugin-security/src/security-plugin.ts (each head diffed against its own merge base with origin/main), so the serial note 6341975560 is cleared.

    Seam measurement, origin/main 72fad922 (TypeScript compiler API over the file; the file is 11,054 lines, the class SecurityPlugin 9,672 lines and 125 members). The plan below is provisional: each cut is re-measured on the merged file before it is cut, and moves exactly one area.

    cut area members (line range at 72fad922) lines
    1 leaf readers: RLS policy collection, unresolved-posture probes, field-level masks, field-name tables collectRLSPolicies .. extractTargetField, the class tail :10338-:11053, except getObjectSecurityMeta ~606
    2 controlled-by-parent read derivation resolveCbpRelation, reportCbpMasterUnresolved, reportRejectedReferenceAlias, computeControlledByParentFilter, controlledByParentChainRefusal (:8827-:9258); the ADR anchor whose invariant names resolveCbpRelation migrates in the same PR ~432
    3 controlled-by-parent write gate checkControlledByParentWrite .. assertMasterRowEditable (:9431-:10336) ~906
    4 write tenant wall and operation principals computeWriteTenantCheckFilter .. writeWallVerdict (:8330-:8825), resolveOperationPrincipals, refuseGuestWithoutOrganization (:9260-:9429) ~666
    5 RLS filter layers computeRlsFilter .. stageRlsMembership (:7621-:8328) ~708
    6 permission-set resolution and addressed writes permissionSetMemoKey .. platformFloorYieldsToObjectWriteModel (:6902-:7619) ~718
    7 read filters, field readers, object admission getReadFilter .. canExport (:5816-:6900) ~1,085
    8 explain, sharing verdicts, authored row write, baselines declareEveryone .. checkAuthoredRowWrite (:4880-:5814) ~935
    9+ start(): the main ql authz middleware (closes over start()'s locals), setup and service registration, init and the seed-ownership claim :1725-:4878 ~3,150

    Module preamble (:1-:1381): a top-level declaration moves with the cut whose bodies alone read it. One cited by a symbol anchor in a file this project does not own (packages/spec/liveness/*.json, e.g. security-plugin.ts#normalizeRequiredPermissions) stays where it is, so getObjectSecurityMeta stays in the class with it for now.

    File surface, cut 1 (stop on a breach and explain it in the report). Move-only, per the card's ask 1-5:


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 25223,
    "status": "done",
    "branch": "claude/project-thread-3fzya4",
    "pr": "#25290",
    "session": "session_017BoLT8Gaa8t9a7AWVH8qZa — this run's harness-stamped id, on the Claude-Session: trailer of both commits",
    "premise_still_valid": true,
    "summary": "Cut 1 of the security-plugin.ts split (series card; the card stays open for cuts 2 and on, per the seam plan in claim 6349703046). The bodies of the 16 leaf readers of SecurityPlugin (collectRLSPolicies, admitCollectedPolicy, warnAuthoredTenantPolicyOnce, resolveUnresolvedCause, probeMetadataOutage, probeUnpublishedDraft, computePartialMaskRules, computeQueryGuardFieldPerms, computeForbiddenFieldWrites, computeReadPartialMaskRules, maskOperationResult, foldFieldRequiredPermissions, getObjectFieldNames, writeCheckFieldOptions, loadObjectFieldNames, extractTargetField) moved unedited to src/security-plugin-object-inputs.ts, with isDelegatedPrincipalLeg and the four imports only they used. The class keeps each private method (name, signature, docblock, position) and calls the body with one host literal typed as the Pick of ObjectInputsHost over exactly the members it reads (#23050 shape B); readonly fields go by value, every other field and the orgScopingEnabled accessor as getters, methods as late-bound closures; six stubs drop async and return the moved function's promise. The rider owed by ACCEPT 6348830068 is in: one docblock sentence in controlled-by-parent-unwritable-master-write.test.ts (ruling 6345869077 item 6). The published d.ts is byte-identical to base, so the ruling 6348891160 bar holds with a raw diff of 0 lines. No open question.",
    "tests": "pnpm --filter @objectstack/plugin-security test (vitest run --maxWorkers=3, under os-verify-lock) at a7cd7c6: Test Files 210 passed (210), Tests 4379 passed | 45 skipped (4424); VERDICT command-exit 0. Base 72fad92 in a separate worktree: the same counts, VERDICT command-exit 0. No tested behaviour changed (move-only), so no ablation. typecheck exit 0 (tsc --noEmit, the scripts tsconfig, check:test-typecheck 0 files / 0 errors). eslint --no-inline-config over the 3 files exit 0. Package build exit 0. Move proof: for all 16 methods the moved body has the same TypeScript AST leaf-token sequence as the base body and each of its 234 lines is the base line minus two leading spaces; parameters, return types and async match; a one-token negative control fails the check. isDelegatedPrincipalLeg verbatim (17 lines), absent from security-plugin.ts. git diff --color-moved=plain --color-moved-ws=allow-indentation-change: 255 of 262 deleted and 318 of 621 added source lines moved; the 7 deleted-not-moved lines are the six private async signature lines and the withOwnershipFloorAlternates import name.",
    "dts_block": {
    "files": "packages/plugins/plugin-security dist/index.d.ts and dist/index.d.mts, base 72fad92 and head a7cd7c6",
    "raw_sha256": "base index.d.ts 61c36a53c30dfccdd4a62280b922bfd1328c83e02253266c469f809c0a78679c · head index.d.ts 61c36a53c30dfccdd4a62280b922bfd1328c83e02253266c469f809c0a78679c · base index.d.mts and head index.d.mts the same hash",
    "normalised_sha256": "base 942a52dc2afc950fdc2f89a720b08d4a8c584250f940db05d099c98f813c2359 · head 942a52dc2afc950fdc2f89a720b08d4a8c584250f940db05d099c98f813c2359 (both files, both sides)",
    "normaliser": "node -e 'const s=require("fs").readFileSync(process.argv[1],"utf8");process.stdout.write(s.replace(/"[^"\n]"(?:\s\|\s*"[^"\n]")+/g,r=>r.split(/\s\|\s*/).sort().join(" | ")))' dist/index.d.ts | sha256sum",
    "raw_diff_lines": 0,
    "positive_control": "head index.d.ts line 2410, private collectRLSPolicies; made public: raw 05654fc4ea1aa556ce256aa3bee1f94c6ec9b5ae1a240c7744e6fe7225d8cd43, normalised 9280d1c333e070fad6635d3adbb9dad308a9ad49a3e3d30cdf90002947f3f738; renamed to collectRLSPoliciesRenamed: raw f20c387525fecc3b2c65bd6113a3c83cfe0388d4defabfc0a5a0fb2149c449db, normalised 312949d507e1943df1f357c43a6a85d97fe8205aefea057ded1bf17866943ef7. Both move the normalised hash.",
    "exports": "ESM and CJS bundles loaded at runtime: 63 names each, base and head; sorted-list sha256 f607fd765d6b277e08b3b46ccb16d1e2d133ec55658ffe40155aef25e1bf54f0 on all four. Bundle bytes differ (code moved within them)."
    },
    "gates": "Local: dispatch-gates --commands, 58 commands, 890 changed lines (under 3000). Each run with its exit captured before any pipe; --ran: 58 derived, 58 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero, every line carries an exit code). check-plugin-teardown-shape --self-test first exited 3 (shallow clone lacked its pinned fixture commit), exit 0 after fetching it; check:dual-build-cjs-loads and check:i18n first exited 3, exit 0 after a full pnpm exec turbo run build under the lock (77 of 77 tasks). Anchor gates exit 0: check:adr-anchors, check-adr-symbol-anchors (2383), check-scripts-symbol-anchors, check-spec-docblock-symbol-anchors, spec check:liveness, check-platform-checklist --changed-from origin/main (6 judged, 0 lost). Census gates base vs head identical except scanned counts moved by the new file (check-undeclared-dep-imports, check:logger-receiver-detach, check:org-identifier). CI on head a7cd7c6, all completed: Build Core success, Test Core (1/6) success, Test Core (2/6) success, Test Core (3/6) success, Test Core (4/6) success, Test Core (5/6) success, Test Core (6/6) success, Test Core success, TypeScript Type Check success, Type Check · workspace success, Type Check · source gates success, Type Check · consumer gates success, Type Check · debt ledger success, Lint & Repo Gates success, Lint · ESLint success, Lint · repo & tooling gates success, Lint · CI, release & engine gates success, Lint · docs & code guards success, Lint · scoped · query-options erasure success, Lint · scoped · slot lookup & corpus success, Lint · scoped · tooling self-tests success, Lint · PM dispatch-gates success, Dogfood Regression Gate success, Governed Surface Queue Guard success, Check Changeset success, Check PR Size success, Check Documentation Links success, Flag docs affected by code changes success, The card this PR closes must claim this branch success, No other open PR may claim the same issue success, No other open PR may claim the same single-writer path success, Part-of PR must not also close its card success, Auto Label success, filter success; skipped by their own filters: Build Docs, Console Pin Gate, Temporal Conformance (live PG + MySQL), Dogfood Verify CLI, the sharded Dogfood Regression Gate matrix, Packed-tarball smoke (opt-in), 18.x upgrade harness (opt-in). No failed or pending check run; the docs-drift comment is advisory (move-only, no documented behaviour changed).",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "11 — reads only: issue_read get x1, issue_read get_comments x1, list_pull_requests x1, pull_request_read get x4, pull_request_read get_check_runs x2, pull_request_read get_comments x1, pull_request_read get_reviews x1. No MCP write tool. Also claude-code-remote subscribe_pr_activity on #25290 (a session subscription, not a GitHub write).",
    "api_writes": "6 strokes, all through the scripts/pm relay: (1) label-write on #25223 (labels pm:dispatched added, pm:queue removed, assignee os-sales; relay run 39842222933); (2) post-stamped claim comment 6349703046 on #25223 (relay run 39842322249); (3) git push of claude/project-thread-3fzya4; (4) fleet-write pr_create, PR #25290 draft with assignee os-sales and label skip-changeset (POST pulls, POST issues/25290/assignees, POST issues/25290/labels; relay run 39849422105; body read back identical); (5) fleet-write pr_request_reviewers os-sales (POST pulls/25290/requested_reviewers; relay run 39849517804); (6) this report, via post-stamped.",
    "open_questions": [],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed cut 1 · domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · 2026-10-22T21:34Z

    reading value
    PR #25290 MERGED through the queue (ACCEPT 6351347337)
    origin/main carries f47f7f222f

    State: the card stays open and dispatched (Part of); the card thread claims cut 2 afresh on the merged file.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p2 · series card, cut 2 of the security-plugin.ts split: the controlled-by-parent read derivation moves out · 2026-10-22T21:47Z
    Session: session_017BoLT8Gaa8t9a7AWVH8qZa
    Account: os-sales (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-3fzya4, restarted from origin/main f47f7f222f after #25290 (cut 1) merged. It is the harness-pinned branch for this session and names no issue, so this claim is the identity record.
    Worktree: objectstack-issue-25223
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    Ruling-ref: 6348891160 (the series' .d.ts bar, item 1)

    Prerequisite, read in this act: #25290 is MERGED (f47f7f222f, landed note 6351823134).

    Serial check, read in this act: of the 14 open PRs, one touches packages/plugins/plugin-security/src/security-plugin.ts: #25288. Its head was diffed against its own merge base with origin/main (cc8d886d). Its hunks are in the import block, in start, in :5146-:6837 (explain, delegated read filter, field readers) and in resolveOperationPrincipals. At that merge base the cut-2 members sit at :8775-:9204, so no hunk of #25288 lies in them. The two changes are disjoint, and whichever lands second takes a plain merge of main.

    Seam re-measured on the merged file (f47f7f222f, 10,929 lines). The cut-2 members are at the same lines the plan named: resolveCbpRelation :8827, reportCbpMasterUnresolved :8963, reportRejectedReferenceAlias :9020, computeControlledByParentFilter :9092 and controlledByParentChainRefusal :9219-:9258. All five are synchronous. Between them they read the members cbpRelCache, ql, logger and each other.

    Module-level declarations, classified by who reads them (TypeScript checker over the file):

    • These move with the cut, because only the moved bodies read them: CBP_LEAF_READ_VERBS, masterNotWritableRefusal, and the imports ReadableIds, controlledByParentMasterRefusal (with its comment) and OWD_DELEGATING_ROW_WRITES.
    • These stay: CBP_MAX_CHAIN_DEPTH, which the write gate also reads and which ADR-0055 anchors by symbol (security-plugin.ts#CBP_MAX_CHAIN_DEPTH); CbpRelation, which the write gate also reads; and RLS_DENY_FILTER and masterGovernsRowWrites, which the module imports from their own modules.
    • The module reads CBP_MAX_CHAIN_DEPTH through an import from security-plugin.ts, so that declaration gains export. index.ts re-exports only SecurityPlugin, so the published surface does not move. The module names CbpRelation through the return type of SecurityPlugin['resolveCbpRelation'], so the interface needs no new export.

    The any-erasure baseline (scripts/query-options-erasure-baseline.json, under ruling on #25224): security-plugin.ts is not one of its keys, the query-options/no-any-erasure rule runs on the file with no exemption, and the cut's range holds no as any at all. So that ruling does not reach this cut.

    File surface, cut 2 (stop on a breach and explain it in the report). Move-only, per the card's ask 1-5:

    • packages/plugins/plugin-security/src/security-plugin.ts: the bodies of the five methods above leave for ONE new module beside it, security-plugin-cbp-read.ts. The module-level declarations that move are the ones listed above. Each method stays in the class with its name, signature, docblock and position. Each hands its body one host literal of exactly the members that body reads (CbpReadHost, each member typed SecurityPlugin['m']). Methods go as late-bound closures, and every field read goes as a getter: cbpRelCache is not declared readonly, and start() assigns ql and logger. This is [maintainer] refactor(rest): split packages/rest/src/rest-server.ts (13,701 lines, 70 commits in 30 days) along its register*Endpoints seams, one move-only slice per PR, /meta after #22877 stage 2 #23050's shape B, as cut 1 applied it.
    • scripts/adr-anchors/: ONE new entry for the new module, carrying ADR-0055 and the §1 invariant of the existing security-plugin.ts entry. That paragraph describes the master derivation, which is now realized there. The security-plugin.ts entry stays unchanged: the method, its docblock and the write half that consumes the derivation remain in that file, and dropping the paragraph there would loosen that file's anchor.
    • Every gate, baseline, docs anchor and test that reads a moved line by path moves in the same PR and stays green. ⛔ No gate is loosened, and none goes silently empty. Both ADR symbol anchors that name this area, security-plugin.ts#computeControlledByParentFilter (ADR-0055, ADR-0139) and #CBP_MAX_CHAIN_DEPTH, keep resolving with no edit, so docs/adr/** is not touched.
    • .d.ts: ruling 6348891160 item 1, with the same four-hash block, normaliser, raw line count and positive control as cut 1. Any other kind of diff stops this thread with needs_decision.
    • No changeset (skip-changeset, ruling 6348891160 item 4).
    • ⛔ Not touched: the write gate (checkControlledByParentWrite onward, cut 3), every other section of the class, any behaviour.
      Container & model: L series (one cut per PR, strictly serial in this thread), card thread (in-session executor), effort: xhigh (permission boundary). The cut goes back to the services seat 1 for ACCEPT before cut 3 starts. A cut over 3000 changed lines lands by human merge.
      Clause-②: no
      Metadata-protocol: none
      Responsibility: n/a — not a defect card
      Thread-read: 6351823134
      Resume-from: none

    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

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions