Repository navigation
[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
Description
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-22attributes 4,847 lines (45.1%) to the last 30 days. - Structure: a 1,247-line module preamble, then one
SecurityPluginclass (9,499 lines, 124 members).start()alone is 3,197 lines: setup and service registration, the mainql.registerMiddlewareauthz 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 |
- Shared state a split must pass:
ql(61 refs),permissionEvaluator(57),logger(43),resolvePermissionSetsForContext(30),tenancyPosture(27), the field-name and CBP caches. Thestart()middleware closes over locals and calls into nearly every later section, so it is the costliest slice; the CBP, RLS and read-filter sections share the caches. - What reads it by path: 9
scripts/files, including the ADR anchorscripts/adr-anchors/packages__plugins__plugin-security__src__security-plugin.ts.json(ADR-0055, ADR-0106, ADR-0123, ADR-0138; its invariant namesresolveCbpRelation, which a CBP slice moves),check-position-name-fold-loaders.mjs,check-system-context-census.mjs,symbol-anchors.mjsand three contract baselines; 28 files undercontent/anddocs/; 171 test files. - In flight: 2 of the 10 open PRs touch the file: fix(objectql,plugin-security): the write preview answers a row's organization from the tenant wall's own verdict #25215 (draft, +560/−297) and fix(plugin-security): a controlled_by_parent detail's read scope is the engine's ReadableIds leaf over its master (ADR-0139 D6, seventh consumer); Q2 A in service-analytics; the Turso remote nested leaf #25198 (draft, +191/−191, the CBP read scope). Slices touching their regions wait for them.
⚠️ This is a permission boundary. Move-only is the whole contract here: a slice that changes any verdict is out of scope.
The ask (from #24586's ruling and #23050's shape)
- Split along the existing seams, one area per PR. Each slice is move-only: no logic changes.
- 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.
- 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.
- Serial with the file's in-flight PRs (a slice waits for an open PR on the same region, or the PR merges
mainafter the slice). - ⛔ 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
objectstack-fleet commentedon Oct 22, 2026
Serial note ·
domain:servicesseat 1 (seat post #6021) ·os-sales·session_016g7nYP9wdNx1GDa5XDr7Uy· 2026-10-22T13:29Z. ⛔ Not a claim. Thread-read: nonepackages/plugins/plugin-security/src/security-plugin.tsis 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
objectstack-fleet commentedon Oct 22, 2026
Claim: card thread ·
domain:servicesseat 1 (seat post #6021, project run by the Sales account) · p2 · series card, cut 1 of thesecurity-plugin.tssplit: 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:41ZSession:
session_017BoLT8Gaa8t9a7AWVH8qZaAccount:
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 frommainafter the previous cut merges.)Worktree:
objectstack-issue-25223Domain:
domain:servicesSeat:
domain:services#1(seat post #6021)Ruling-ref: 6348891160 (the series'
.d.tsbar, 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 withorigin/main), so the serial note6341975560is cleared.Seam measurement,
origin/main72fad922(TypeScript compiler API over the file; the file is 11,054 lines, the classSecurityPlugin9,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.72fad922)collectRLSPolicies..extractTargetField, the class tail:10338-:11053, exceptgetObjectSecurityMetaresolveCbpRelation,reportCbpMasterUnresolved,reportRejectedReferenceAlias,computeControlledByParentFilter,controlledByParentChainRefusal(:8827-:9258); the ADR anchor whose invariant namesresolveCbpRelationmigrates in the same PRcheckControlledByParentWrite..assertMasterRowEditable(:9431-:10336)computeWriteTenantCheckFilter..writeWallVerdict(:8330-:8825),resolveOperationPrincipals,refuseGuestWithoutOrganization(:9260-:9429)computeRlsFilter..stageRlsMembership(:7621-:8328)permissionSetMemoKey..platformFloorYieldsToObjectWriteModel(:6902-:7619)getReadFilter..canExport(:5816-:6900)declareEveryone..checkAuthoredRowWrite(:4880-:5814)start(): the mainqlauthz middleware (closes overstart()'s locals), setup and service registration,initand the seed-ownership claim:1725-:4878Module 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, sogetObjectSecurityMetastays 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:
packages/plugins/plugin-security/src/security-plugin.ts: the bodies of 16 private methods leave for ONE new module beside it,packages/plugins/plugin-security/src/security-plugin-object-inputs.ts, with the module-level declarations only they read (isDelegatedPrincipalLeg, the imports only they use). The class keeps each method (name, signature, docblock, position) and hands its body one host literal of exactly the members that body reads, each typed as the class member (SecurityPlugin['m']), methods as late-bound closures, fields reassigned after construction as getters ([maintainer] refactor(rest): splitpackages/rest/src/rest-server.ts(13,701 lines, 70 commits in 30 days) along itsregister*Endpointsseams, one move-only slice per PR,/metaafter #22877 stage 2 #23050's shape B, as [maintainer] refactor(service-automation): splitsrc/engine.ts(13,735 lines, +2,199 in 30 days) along itsAutomationEngineseams, one move-only slice per PR #25222 and [maintainer] refactor(plugin-auth): splitsrc/auth-manager.ts(10,338 lines, +1,867 in 30 days) along itsAuthManagerseams, one move-only slice per PR #25224 cut 1 apply it).packages/plugins/plugin-security/src/controlled-by-parent-unwritable-master-write.test.ts: one docblock sentence saying why the raw registration door alone pins the resolution (ruling6345869077item 6; owed by the seat's ACCEPT6348830068on PR fix(plugin-security)!: under a controlled_by_parent declaration whose one master reference is readonly or system, every write that sets it is refused (ADR-0055 §1) #25256 to this card). Comment only..d.ts: ruling6348891160item 1 — the publisheddist/index.d.tsanddist/index.d.mtskeep the same declaration set (no private member made public or protected, nothing added, removed, renamed or retyped), are byte-identical to base after sorting the members of each string-literal union and nothing else, keep identical ESM and CJS export lists, and every raw-diff line is a union-member-order swap. Any other diff kind stops this thread withneeds_decision.skip-changeset, ruling6348891160item 4).getObjectSecurityMeta, every other section of the class, any behaviour.Container & model:
Lseries (one cut per PR, strictly serial in this thread), card thread (in-session executor),model: default tier(dispatch-gates.mjs --tier --repo objectstack-ai/objectstack: "no path-derived mandate"),effort: xhigh(permission boundary). Each cut goes back to the services seat 1 for ACCEPT before the next 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: 6341975560
Serial constraints cleared:
security-plugin.tsor the test file above.src/engine.ts(13,735 lines, +2,199 in 30 days) along itsAutomationEngineseams, one move-only slice per PR #25222 (service-automation) and [maintainer] refactor(plugin-auth): splitsrc/auth-manager.ts(10,338 lines, +1,867 in 30 days) along itsAuthManagerseams, one move-only slice per PR #25224 (plugin-auth) are other files in other packages: disjoint.security-plugin.ts.Resume-from: none
Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
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
objectstack-fleet commentedon Oct 22, 2026
Landed cut 1 ·
domain:servicesseat 1 (seat post #6021) ·os-sales·session_016g7nYP9wdNx1GDa5XDr7Uy· 2026-10-22T21:34Z6351347337)origin/mainf47f7f222fState: the card stays open and dispatched (
Part of); the card thread claims cut 2 afresh on the merged file.Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Claim: card thread ·
domain:servicesseat 1 (seat post #6021, project run by the Sales account) · p2 · series card, cut 2 of thesecurity-plugin.tssplit: the controlled-by-parent read derivation moves out · 2026-10-22T21:47ZSession:
session_017BoLT8Gaa8t9a7AWVH8qZaAccount:
os-sales(the seat's linked user; the card's assignee)Branch:
claude/project-thread-3fzya4, restarted fromorigin/mainf47f7f222fafter #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-25223Domain:
domain:servicesSeat:
domain:services#1(seat post #6021)Ruling-ref: 6348891160 (the series'
.d.tsbar, 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 withorigin/main(cc8d886d). Its hunks are in the import block, instart, in:5146-:6837(explain, delegated read filter, field readers) and inresolveOperationPrincipals. 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 ofmain.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:9092andcontrolledByParentChainRefusal:9219-:9258. All five are synchronous. Between them they read the memberscbpRelCache,ql,loggerand each other.Module-level declarations, classified by who reads them (TypeScript checker over the file):
CBP_LEAF_READ_VERBS,masterNotWritableRefusal, and the importsReadableIds,controlledByParentMasterRefusal(with its comment) andOWD_DELEGATING_ROW_WRITES.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; andRLS_DENY_FILTERandmasterGovernsRowWrites, which the module imports from their own modules.CBP_MAX_CHAIN_DEPTHthrough an import fromsecurity-plugin.ts, so that declaration gainsexport.index.tsre-exports onlySecurityPlugin, so the published surface does not move. The module namesCbpRelationthrough the return type ofSecurityPlugin['resolveCbpRelation'], so the interface needs no new export.The
any-erasure baseline (scripts/query-options-erasure-baseline.json, under ruling on #25224):security-plugin.tsis not one of its keys, thequery-options/no-any-erasurerule runs on the file with no exemption, and the cut's range holds noas anyat 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 typedSecurityPlugin['m']). Methods go as late-bound closures, and every field read goes as a getter:cbpRelCacheis not declaredreadonly, andstart()assignsqlandlogger. This is [maintainer] refactor(rest): splitpackages/rest/src/rest-server.ts(13,701 lines, 70 commits in 30 days) along itsregister*Endpointsseams, one move-only slice per PR,/metaafter #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 existingsecurity-plugin.tsentry. That paragraph describes the master derivation, which is now realized there. Thesecurity-plugin.tsentry 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.security-plugin.ts#computeControlledByParentFilter(ADR-0055, ADR-0139) and#CBP_MAX_CHAIN_DEPTH, keep resolving with no edit, sodocs/adr/**is not touched..d.ts: ruling6348891160item 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 withneeds_decision.skip-changeset, ruling6348891160item 4).checkControlledByParentWriteonward, cut 3), every other section of the class, any behaviour.Container & model:
Lseries (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