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. added
    enhancementNew feature or request
    domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landed
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    on Oct 22, 2026
  2. 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

  3. 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

  4. 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

  5. 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

  6. 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

  7. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: needs_decision fork review (project review thread 「#25223 ADR 锚点裁决」) · letter A · maintainer 「对于这种类似的问题,能否你自己开一个单独的 fable thread 做评审,对于自己有把握决定的问题,自己决定,不能决定的开决策卡?」 2026-10-22T22:52Z

    Proxy ruling under the maintainer's delegation quoted above (carried into this project by its owner's standing rule 「卡线程报 needs_decision 时,另开 fable 裁决线程,有把握就裁并把裁决落在 GitHub 上,没把握才给 Bill 发决策卡」), by the review thread's session session_01XtTny7MNZWVR3HdqvRigrN (project thread), on the domain:services seat 1 card thread's needs_decision fork for cut 2 of this card (claim 6352049267, session session_017BoLT8Gaa8t9a7AWVH8qZa; the fork reached the coordinator from the thread and is recorded on this card for the first time here). The card thread's next comment here carries Ruling-ref: this comment's id. Marker: auto-adjudicated. ⚠️ Reversible on one word from the maintainer; nothing here forecloses that.

    The question

    Cut 2 as claimed in 6352049267 moves the five controlled-by-parent read-derivation bodies (resolveCbpRelation, reportCbpMasterUnresolved, reportRejectedReferenceAlias, computeControlledByParentFilter, controlledByParentChainRefusal) out of security-plugin.ts into security-plugin-cbp-read.ts. At its head 94ef3e320f (base 5801a4f2, which is origin/main at this write) the 62 derived gates are green, the .d.ts four-hash block holds and the plugin-security suite is 210/210, but node scripts/check-adr-symbol-anchors.mjs, which CI runs (.github/workflows/lint.yml, step adr-symbol-anchors-resolve-no-line-numbers) and dispatch-gates --commands did NOT derive for this change set, exits 1 at the head and 0 at the base:

    • docs/adr/0055-master-detail-controlled-by-parent.md:26 security-plugin.ts#sharingModel — unresolved-symbol
    • docs/adr/0055-master-detail-controlled-by-parent.md:58 security-plugin.ts#controlled_by_parent — unresolved-symbol

    Both anchors sit in sentences about the WRITE half of ADR-0055 — Context item 3, 「By-id write pre-image check (#1994). …security-plugin.ts#sharingModel already re-reads the target row under the write-op RLS filter before an update/delete and denies if invisible. This is the exact hook to extend with a master-access check.」, and §3, 「In the pre-image block (…security-plugin.ts#controlled_by_parent), for a controlled_by_parent detail update/delete/create」 — yet both resolve only on tokens inside the READ derivation. docs/adr/** is Tier H (AGENTS.md Prime Directive 14): an edit there lands only on the maintainer's APPROVED review. The fork:

    • A (the card thread's recommendation, in its second, narrower reading) — cut 2 moves only resolveCbpRelation and its two reports reportCbpMasterUnresolved and reportRejectedReferenceAlias into a new module security-plugin-cbp-relation.ts; computeControlledByParentFilter and controlledByParentChainRefusal stay in security-plugin.ts with the declarations only they read; the module keeps a type-only import of the class (cut 1's shape) and CBP_MAX_CHAIN_DEPTH needs no export; the two misdirected anchors go on record as an out-of-scope finding and are repointed in a separate Tier H docs PR.
    • B — repoint the two anchors inside the cut-2 PR; the whole PR becomes Tier H and waits for the maintainer.
    • C — a separate Tier H docs PR first; cut 2 in its five-body shape waits for it to land.

    This thread's re-measure

    Fresh clone at 5801a4f2. node scripts/check-adr-symbol-anchors.mjs: base exit 0 (2383 anchors across 142 records resolve); head 94ef3e320f in a worktree: exit 1 with exactly the two findings above. Then every ADR symbol anchor naming security-plugin.ts — 28 anchors across 10 ADRs — was mapped to the lines it resolves on, with extractAnchors and symbolResolutionClass from scripts/symbol-anchors.mjs (the gate's own resolver, comments stripped as it strips them):

    • #sharingModel (ADR-0055:26) resolves as declaration on ONE site, base :9208, const sharingModel = schema?.sharingModel ?? …, a local inside computeControlledByParentFilter. Its only other spelling in the file is a docblock (:9121), which the resolver strips.
    • #controlled_by_parent (ADR-0055:58) resolves as literal on THREE sites: :9035 (a quoted token inside a template literal in reportCbpMasterUnresolved), :9209 (computeControlledByParentFilter) and :9275 (controlledByParentChainRefusal). Its 23 other spellings are comments and docblocks (:546–:615, :5857, :7349, …), stripped before matching.
    • ⭐ The write half the two sentences describe — checkControlledByParentWrite :9492 through assertMasterRowEditable :10132 — holds no 'controlled_by_parent' token and binds no sharingModel. These two anchors have never pointed at the mechanism their sentences name; they resolve by coincidence of spelling. The gate's header says why it cannot see that: 「It verifies that the anchor POINTS AT SOMETHING REAL. It cannot verify that the sentence around the anchor is TRUE about that code.」
    • The narrowed cut, simulated on the base file by blanking :8884–:9199 (the three bodies to move): all 28 anchors still resolve, #sharingModel on :9208, #controlled_by_parent on :9209 and :9275. No other anchor resolves only inside those three bodies. Every method-name anchor into the file (#computeControlledByParentFilter at ADR-0055:5, :123 and ADR-0139:246; #assertControlledByParentWrite; #assertMasterRowEditable; #hasPlatformAdminPosture; #collectRLSPolicies; #computeDelegatedReadFilter; #computeRlsFilter; #getReadFilter; the two #SecurityPlugin.… members) resolves on a declaration the class keeps as a method shell under the series' shape, so no move-only cut can break it.
    • Three MORE anchors of the same class as the two above sit in bodies later cuts move, read now so no later cut discovers them on a built head: ADR-0123:4 #refuseNoActiveOrganization resolves ONLY on the local const refuseNoActiveOrganization = (…) at :8499, inside composeWriteTenantWall (cut 4's write tenant wall); ADR-0055:24 #rlsFilter ONLY on the local const rlsFilter = andComposeLayers(…) at :4359, and ADR-0126:363 #nav_permission_sets ONLY on the literal at :1789, both inside start() (the cut-9+ middleware, over the 3,000-line human-merge line by the seam plan). Four module-level declarations are anchored by symbol and stay where they are under the series' own rule for anchored top-level symbols: #isPrincipalLessContext (:484), #CBP_MAX_CHAIN_DEPTH (:630), #writeCheckPolicies (:1279), #manage_metadata (:233, also :4888).

    Governing text (verbatim, at origin/main 5801a4f2)

    • AGENTS.md Prime Directive 14: 「The governed surfaces are docs/adr/**, … a mixed diff is governed whole on one path hit.」 「Authoring stays open to every seat — drafting, pushing, opening and revising the PR. What is reserved is the landing … a mixed diff is not a proportion question, one path hit is enough; to land the rest, split off the governed files. The landing is tiered by the register; one Tier H path makes the whole PR Tier H.」
    • scripts/check-adr-symbol-anchors.mjs header: 「[unresolved-symbol] The file is there, the symbol is not. Either the symbol was renamed (update the anchor) or the mechanism moved out of that file (re-anchor it where it lives). ⚠️ If the SENTENCE has become untrue -- not just the pointer -- that is a separate defect and takes its own card … Do not repair prose to clear this gate.」 and 「⛔ MAINTAINER-ONLY: adding an anchor-exempt marker. … An author whose anchor will not resolve fixes the ANCHOR, never the exemption list.」
    • scripts/symbol-anchors.mjs header: 「⚠️ literal is deliberately WEAKER than declaration and the migration prefers declaration wherever one exists.」
    • The card: ask 1 「Each slice is move-only: no logic changes」; ask 3 「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.」; ask 5 「⛔ No new gate.」
    • The cut-1 claim 6349703046, the series' own precedent for an anchored body: 「One cited by a symbol anchor in a file this project does not own … stays where it is, so getObjectSecurityMeta stays in the class with it for now.」 and 「A reader in another lane's file is a declared cross-lane touch, or the cut is narrowed so it is not needed.」
    • .claude/skills/pm-dispatch/SKILL.md 〈升级与决策〉: 「具名不升级类(立即行动):恢复不变量;技术任务间顺序与依赖;验证策略;说明书脱节。」; 「纯文档措辞更正(无契约声明改动);既有门禁内部参数与盲区修复(加强,非削弱,非新增)。」; 「裁决一次裁清:子问题、分叉与执行参数同笔裁定」. references/triage-duties.md: 「代裁人工地板(恒交维护者):功能新增;ADR;协议/公开契约变化;破坏性或难回滚动作。」
    • Ruling 6351993113 on [maintainer] refactor(plugin-auth): split src/auth-manager.ts (10,338 lines, +1,867 in 30 days) along its AuthManager seams, one move-only slice per PR #25224, item 6: 「when a per-file shrink-only ledger names the big file by path and a counted site sits inside a body to be moved, the site is swept in its own PR first, in the ledger's own prescribed direction」; ruling 6348891160 on [maintainer] refactor(service-automation): split src/engine.ts (13,735 lines, +2,199 in 30 days) along its AutomationEngine seams, one move-only slice per PR #25222 (the series' .d.ts bar), unchanged by this ruling.

    Ruling: A, as narrowed

    Sequencing between two tasks the repo already keeps apart — a move-only cut and a Tier H docs correction — is a named no-escalation class (「技术任务间顺序与依赖」); the anchor reading this ruling adds to the series' proof is 「验证策略」. No floor moves: behaviour is identical (move-only, same suite), the published declarations keep the 6348891160 bar, no gate is loosened (the anchor gate stays green on the real tokens, with no exemption and no planted token), nothing is added. The ADR's words are NOT decided here: the repoint is proposed in its own PR and becomes true only on the maintainer's APPROVED review, which is the Tier H control working as written. The PM answers this fork; it does not go up.

    Why not B. AGENTS 14 answers it in as many words: one docs/adr/** path makes the whole 400-line move PR Tier H, and the prescribed cure for a mixed diff is 「split off the governed files」. B would also bury a two-line pointer correction the maintainer must read on its own inside a diff that is, by the card's contract, content-free. And the gate's remedy 「re-anchor it where it lives」, read literally for these two sentences, would point write-path prose at a read module: the sentences name the write half, which does not move in cut 2.

    Why not C. The three relation bodies need no anchor edit at all (re-measure above), so holding them behind a Tier H review buys nothing; the series is strictly serial in one thread on one harness-pinned branch, so a cut that waits for the maintainer stalls cuts 3+ with it. 6351993113 ordered a sweep PR first because that sweep was self-landable in the lane; a Tier H docs PR is not, so the order inverts: move what needs no doc edit now, and let the docs PR gate only the bodies that carry the tokens.

    Fixed here, so no later cut of this card opens a second decision on the same point:

    1. Cut 2 (narrowed). Moves exactly resolveCbpRelation, reportCbpMasterUnresolved and reportRejectedReferenceAlias into ONE new module beside the file, security-plugin-cbp-relation.ts, with the module-level declarations and imports only those three bodies read; computeControlledByParentFilter, controlledByParentChainRefusal, CBP_LEAF_READ_VERBS, CBP_MAX_CHAIN_DEPTH, CbpRelation and every import they still need stay in security-plugin.ts. The module imports the class type-only (cut 1's shape); ⛔ no new export from security-plugin.ts (the two-way value import of 6352049267 is withdrawn with the narrowing). docs/adr/** untouched. Everything else in 6352049267 stands: shape B hosts, getters for the non-readonly fields, the .d.ts block per 6348891160, skip-changeset, the scripts/adr-anchors/ entry for the new module carrying the ADR-0055 §1 invariant while the security-plugin.ts entry keeps its paragraph (scripts/adr-anchors/ is not a row of the governed register). An amended claim via the relay precedes the PR, carrying Ruling-ref: this comment.
    2. The anchor reading joins the series' report block, every cut from now on (dispatch-gates does not derive it for this change set): node scripts/check-adr-symbol-anchors.mjs at the cut's base AND head, both exit 0 with the printed anchor count; plus the map of every ADR symbol anchor naming the big file, each with the line(s) it resolves on and its class (declaration or literal), classified stays / moves / new module. A cut whose map shows an anchor resolving ONLY inside a body it would move stops BEFORE cutting and takes item 4's route — never a needs_decision after the head is built. The map is measurement with symbol-anchors.mjs's exported functions, ⛔ not a new gate (ask 5).
    3. The body-token anchors go into ONE docs-only card. Cut 2's os-dev-report lists them under out_of_scope_findings, and the services seat files one card under the filing gate (domain:services, S, Tier H, no code), with this reading as its premise: ADR-0055:26 #sharingModel resolves on a local const inside the read derivation and ADR-0055:58 #controlled_by_parent on string literals there, while both sentences describe the by-id write pre-image check; ADR-0123:4 #refuseNoActiveOrganization resolves on a local closure inside composeWriteTenantWall, a body cut 4 moves. The card's PR changes those three anchors and nothing else — ⛔ no prose edit (the gate's header: 「Do not repair prose to clear this gate」), ⛔ no anchor-exempt marker (maintainer-only) — to declaration-class symbols the class keeps as method shells across every cut, so no later move can break them again. Candidate targets, for the PR author to propose and the maintainer to approve or amend: ADR-0055:58 → security-plugin.ts#checkControlledByParentWrite (the §3 gate's entry, :9492); ADR-0055:26 → #checkControlledByParentWrite as well (it is the hook 「to extend with a master-access check」 the sentence names), or a file-level anchor if the author reads the fix(security)[P0]: enforce RLS on by-id writes — close member-edits-others'-records hole (#1985) #1994 pre-image step (inline in start(), step 2.7) as naming no symbol — the gate's remedy for that case; ADR-0123:4 → #composeWriteTenantWall. The PR lands only on an APPROVED review by a GOVERNED_APPROVERS account; the director seat requests it in a Tier H batch; no seat readies, queues or arms it before.
    4. The remainder of cut 2 (computeControlledByParentFilter, controlledByParentChainRefusal, CBP_LEAF_READ_VERBS and the imports only they read) moves in a later cut ONLY after item 3's PR is on main, under the series' usual proof plus item 2's reading, grouped with the write gate or on its own as the thread claims; the same holds for composeWriteTenantWall in cut 4. Until then the bodies stay as they are. ⛔ Never plant a token to keep an anchor green (a const sharingModel, a quoted 'controlled_by_parent'; a comment does not count anyway); ⛔ never move a body an anchor resolves only inside of. If the maintainer declines item 3, those bodies stay in security-plugin.ts for the series' end state and the final report says so; that is not a fork.
    5. Cuts 3 onward are not blocked by items 3 or 4. At 5801a4f2 every other symbol anchor into the file resolves on a method or class declaration the class keeps, on a module-level declaration that stays, or on a token outside the bodies cuts 3–8 move; item 2's reading per cut is what proves it each time. The two start() locals (#rlsFilter ADR-0055:24, #nav_permission_sets ADR-0126:363) are a different case from this ruling's: there the sentence's mechanism itself moves, so the gate's literal remedy 「re-anchor it where it lives」 applies and the cut re-anchors them to the new module in the same PR — a Tier H PR by construction, which that cut already is by the 3,000-line human-merge line; its claim says so up front rather than reporting a fork. A top-level declaration an ADR anchors by symbol (isPrincipalLessContext, CBP_MAX_CHAIN_DEPTH, writeCheckPolicies, the manage_metadata table) stays in security-plugin.ts, by the cut-1 precedent for getObjectSecurityMeta, unless a Tier H repoint lands first.
    6. Premise, falsifiable: at cut 2's base, blanking the three relation bodies leaves every ADR symbol anchor into security-plugin.ts resolving (this thread's reading at 5801a4f2). A later base where that fails — a new anchor into the file, a renamed local — falsifies it: stop and report the fork on this card, ⛔ do not widen the cut, ⛔ do not edit docs/adr/** inside the cut.
    7. A tooling finding, noted not ruled: dispatch-gates --commands derived 62 gates for a change set that moved lines ADR anchors resolve on and did not derive check-adr-symbol-anchors; cut 1 ran it by hand. The card thread lists it in out_of_scope_findings for a domain:cli finding card (「既有门禁…盲区修复」 class); ⛔ no scripts/pm/ edit under this card.

    Why A, on the four axes (business standpoint)

    • ① Long-term soundness (weight ≥50%). Two years out, every ADR anchor into this file names a declaration the class keeps — the resolver's own stated preference (「prefers declaration wherever one exists」) — and no split, no renamed local and no reworded message can rot it silently again. A gets there with the ADR corrected once, by the maintainer, on its own three-line diff; B and C reach the same text but hold a content-free move behind a governance review, and B's literal 「re-anchor where it lives」 would point write-path prose at a read module.
    • ② Measured business pull. The maintainer's split order ([Decision] Split the platform's largest, fastest-growing source files (protocol.ts 32,428 lines, +10,002 in 30 days) the way rest-server.ts was split, or leave them #24586 「同意A」), three cards in flight; the controlled-by-parent section is 1,541 lines, 774 from the last 30 days. Zero consumer sees any option: move-only, declarations under the 6348891160 bar.
    • ③ AI-error prevention. Both failure modes stay loud: an anchor whose token moves reds the CI gate (reproduced at 94ef3e320f), and the only quiet cures — an exemption marker, a planted token, a prose edit — are forbidden by name in the gate's header and in items 3 and 4. Item 2 moves the reading BEFORE the cut, so the red is met on the map, not after a built head.
    • ④ Startup no-sprawl. No gate, no script, no changeset, no second module beyond the one the cut needs; one three-line docs PR and two filed findings.

    Only-① pick: A. Do ②③④ flip it: no. Effort for the chosen letter: cut 2 re-cut on three bodies (the branch already holds the five-body shape to narrow), one docs card with a three-line PR, one tooling finding.

    os-decision-facets
    ① 长远:A 让这三个只靠函数体内 token 解析的 ADR 锚点一次改到类上保留的方法声明(解析器自述「宁取 declaration」),此后任何搬动、局部变量改名或文案改写都不再能悄悄锈蚀它;搬动本身不碰 docs/adr/**,受管面各走各道(AGENTS 14「split off the governed files」)。B 把三行文档修正埋进四百行无内容的搬动 diff 等 Bill;C 为不需要文档改动的三个函数体白等一轮审批并卡住后续各切。
    ② 拉动:维护者 #24586「同意A」拆分令,三张拆分卡在飞;CBP 段 1,541 行、30 天新增 774 行。任何选项对消费者零可见:只搬不改,.d.ts 按 6348891160 的判据。
    ③ 防错:token 随函数体搬走即 CI 红(94ef3e3 已复现);三条静默出路——anchor-exempt 标记、种一个 token、改文案——门禁头注与本裁决第 3、4 条逐一点名禁止;第 2 条把锚点读数前移到切之前,红在图上见而不在建好的头上见。
    ④ 不扩散:不加门禁、不加脚本、不加 changeset;一张三行的文档 PR,两张 finding 卡。
    Prior rulings read: anchor,symbol,move-only,resolver,unresolved-symbol,governed → 23 hits; ADR-0131 D7, ADR-0007 Decision §5, ADR-0007 Decision §8, ADR-0030 Decision §1, ADR-0030 Decision §6, ADR-0045 Decision §3, ADR-0053 Decision §7, ADR-0057 D9 (term noise on resolver / governed; none decides an ADR symbol anchor across a file split, read); thread: none by the script's first-line shape (read by hand: 6341975560, 6349703046, 6351250429, 6351823134, 6352049267); other cards: #25222 6348891160, #25224 6351993113; the maintainer's anchor ruling of 2026-09-01 as the gate's header records it (symbol anchors plus a resolver gate, 「C 不作过渡」, exemptions maintainer-only); repo: objectstack-ai/objectstack

    Execution after the ruling

    • Thread-read: 6352049267 (the newest comment on this card at this write). The card thread re-cuts cut 2 to the three relation bodies on its branch, posts an amended claim via the relay with Ruling-ref: this comment at line start, and reports with item 2's block; services seat 1 ACCEPTs on it.
    • The seat files item 3's docs card and item 7's tooling finding from the report; the director seat carries the docs PR to the maintainer in its next Tier H batch.
    • ⛔ Ruling is not dispatching; the thread already holds its slot. No decision card to the maintainer; this comment is the record, reversible on one word.
    • Cross-lane: none in cut 2. The docs card's PR touches docs/adr/** only (Tier H); the tooling finding is the cli seat's.
  8. 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, AMENDED: the controlled-by-parent master relation moves out · 2026-10-22T22:55Z
    Ruling-ref: 6353073966 (cut 2's scope, letter A as narrowed); the series' .d.ts bar stays ruling 6348891160 item 1
    Supersedes: 6352049267 (cut 2's five-body claim; its surface is narrowed here by the ruling, everything it does not name below stands)
    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 this claim is the identity record. It already holds the five-body shape (abdb89ad03, merged with origin/main at 94ef3e320f); the narrowed cut is one more commit on top, and the PR's diff against its merge base 5801a4f2c is the narrowed cut alone.
    Worktree: objectstack-issue-25223
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)

    Why amended: at the five-body head 94ef3e320f, node scripts/check-adr-symbol-anchors.mjs exits 1 (base 5801a4f2c exits 0). ADR-0055:26 security-plugin.ts#sharingModel and ADR-0055:58 security-plugin.ts#controlled_by_parent resolve only on tokens inside computeControlledByParentFilter and controlledByParentChainRefusal. The fork went to review, and ruling 6353073966 chose A.

    File surface, cut 2 as ruled (stop on a breach and explain it in the report):

    • packages/plugins/plugin-security/src/security-plugin.ts: the bodies of resolveCbpRelation, reportCbpMasterUnresolved and reportRejectedReferenceAlias leave for ONE new module beside it, security-plugin-cbp-relation.ts. With them go the one module-level declaration only they read (masterNotWritableRefusal) and the two imports only it reads (controlledByParentMasterRefusal with its comment, and OWD_DELEGATING_ROW_WRITES). Each method stays in the class with its name, signature, docblock and position, and hands its body one host literal of exactly the members that body reads (CbpRelationHost). Methods go as late-bound closures. ql, logger and cbpRelCache go as getters, because none is declared readonly. 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.
    • These stay in security-plugin.ts, byte for byte: computeControlledByParentFilter, controlledByParentChainRefusal, CBP_LEAF_READ_VERBS, CBP_MAX_CHAIN_DEPTH, CbpRelation and every import they read (ruling item 1). The module imports the class type-only, and security-plugin.ts gains no export. The two-way value import of 6352049267 is withdrawn.
    • scripts/adr-anchors/: ONE new entry for the new module, carrying ADR-0055 and the §1 invariant of the existing security-plugin.ts entry. The security-plugin.ts entry stays unchanged.
    • docs/adr/**: not touched.
    • 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.
    • .d.ts: ruling 6348891160 item 1, with the same four-hash block, normaliser, raw line count and positive control as cut 1.
    • No changeset (skip-changeset).
    • ⛔ Not touched: the write gate (checkControlledByParentWrite onward), every other section of the class, any behaviour.

    Anchor reading before the cut (ruling item 2): every ADR symbol anchor into security-plugin.ts was mapped with extractAnchors and symbolSegmentResolution from scripts/symbol-anchors.mjs. The map covers 20 distinct symbols and 28 anchors across 10 ADRs. At the narrowed head, each one resolves in security-plugin.ts with the same class it had at the base. #sharingModel stays a declaration and #controlled_by_parent stays a literal, both on the bodies that stay. No anchor resolves only inside the three bodies that move. node scripts/check-adr-symbol-anchors.mjs exits 0 at both base and head, with 2383 anchors, 440 of them declaration-class and 27 literal-class. That holds premise 6 of the ruling.

    Since 6352049267, origin/main moved to 09e07c259a. Nothing under packages/plugins/plugin-security/, docs/adr/ or scripts/adr-anchors/ changed, so the reading stands on the PR's base.

    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.
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: 6353073966
    Resume-from: none


    Generated by Claude Code

  9. 18 remaining items

  10. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed cut 7 · domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · 2026-10-23T10:29Z

    reading value
    PR #25401 MERGED through the queue (ACCEPT 6362377482)
    origin/main carries 2dfeb1a772

    State: the card stays open and dispatched (Part of). The card thread claims plan row 6, permission-set resolution and addressed writes, afresh on the merged file. The start() cuts are Tier H and come back to the seat before any PR is opened.


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p2 · series card, cut 8 of the security-plugin.ts split: plan row 6, the permission-set resolution and the addressed-write readers, move out · 2026-10-23T10:54Z
    Ruling-ref: 6353073966 (item 2 for the anchor reading below); the series' .d.ts bar stays ruling 6348891160 item 1
    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, restarted from origin/main at 2dfeb1a772, cut 7's merge. It names no issue, so this claim is the identity record.
    Worktree: objectstack-issue-25223
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)

    Why now: the landing record 6362857718 hands the card thread plan row 6, claimed afresh on the merged file. The plan in the cut 1 claim 6349703046 lists it as permission-set resolution and addressed writes. No start() code is in this cut.

    File surface, cut 8 (stop on a breach and explain it in the report). Row 6 has two seams, so the bodies go to TWO new modules in this one PR, as cut 6 split its bodies over two modules.

    Anchor and reader reading before the cut (ruling item 2), on 2dfeb1a772, with the trial cut in place rather than blanked bodies:

    • ADR anchors: every ADR symbol anchor into security-plugin.ts was mapped with extractAnchors and symbolSegmentResolution from scripts/symbol-anchors.mjs. That is 27 anchors over 18 symbols, and none changes class at the trial head. ADR-0138:427 #SecurityPlugin.resolvePermissionSetsForContextUnmemoized resolves on the method, which stays. Neither new module resolves any of them.
    • Every security-plugin.ts#… anchor anywhere in the repo (30 symbols, the spec liveness ledger and the platform checklist areas included) resolves with its base class at the trial head: 0 changed.
    • node scripts/check-adr-symbol-anchors.mjs exits 0 at base and at the trial head, with the same line: 2384 anchors, 440 declaration-class and 26 literal-class.
    • Censuses, with the two modules tracked (git add -N), compared site by site with the module paths mapped back:
      • isystem-census: 148 sites at both ends, 0 differences, none in the modules.
      • tenant-audit-census: 238 sites, 0 differences, 174 non-engine subtractions at both ends, exit 0.
    • Readers by path:
    • Prose that names security-plugin.ts for the position-name fold stays true by method, because SecurityPlugin#resolvePermissionSetsForContextUnmemoized is still there. Those are the lint.yml comment on the Permission-set resolution binds by POSITION NAME while sys_position_permission_set sits near-empty — confirm name-based resolution is the intended mechanism, not an accident the empty junction hides #13419 gate, the message text of check-position-name-fold-loaders.mjs, and the comment in measure-position-name-fold-census.mjs.
      • check-position-name-fold-loaders.mjs scans the tree only for references to the HotCRM artifact, which no moved body names. It and its --self-test exit 0 at the trial head.
      • measure-position-name-fold-census.mjs scans plugin-security for export const string constants, so it would now see POSITION_NAME_FOLD_EVENT. Run at base and at the trial head, its output is byte-identical: it already refuses on its own failing self-test at base. CI does not run it.

    Serial check: of the 13 open PRs, none touches security-plugin.ts, the new module names, explain-engine.ts, delegated-admin-gate.ts, the census scripts, the permissions docs pages, or the plugin-security anchor entry. #25389 edits the scan scope of the two @objectstack/core pins above (an extension-scoped prefilter), not what either pin discriminates on.

    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 9 starts.
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: 6362857718
    Resume-from: none


    Generated by Claude Code

  12. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 25223,
    "status": "done",
    "branch": "claude/project-thread-3fzya4",
    "pr": "#25406",
    "session": "session_017BoLT8Gaa8t9a7AWVH8qZa — this run's harness-stamped id, on the Claude-Session: trailer of every commit on the branch",
    "premise_still_valid": true,
    "summary": "Cut 8 of the security-plugin.ts split, row 6 of the cut 1 plan (permission-set resolution and addressed writes); claim 6363282332 (Ruling-ref 6353073966; d.ts bar 6348891160). The row has two seams, so the bodies of nine private methods moved unedited to two new modules in this one PR, as cut 6 did. To src/security-plugin-permission-sets.ts (host PermissionSetsHost): resolvePermissionSetsForContextUnmemoized (positions folded into the request, the human baseline or the agent restricted ceiling, the post-resolution fallback), dropDeactivatedFoldedSets, reportNameFoldCollisions (#13419 / #24757) and permissionSetMemoKey (#10757, called directly). To src/security-plugin-addressed-writes.ts (host AddressedWritesHost, one member, ql): addressedByIdWriteId and addressedPredicateWrite (#21771 / #21829, called directly), readableRowsScopeForPredicateWrite (#21829), extractSingleId (called directly; its docblock stays above declaresControlledByParent with the next one) and readRowById (#7505). Shape B as in cuts 1 to 7: each method keeps name, signature, docblock and position less async and hands its body one host literal typed as the Pick of its host over exactly the members it reads; methods as late-bound closures, the readonly baselinePermissionSets, bootstrapPermissionSets and nameFoldWarned by value, logger, metadata, permissionEvaluator and ql as getters. Seven members of the row stay whole: getEffectiveObjectPermissions, resolveFallbackPermissionSets, declaresControlledByParent, callerCannotReadAddressedRow and getCallerPreImage (each body no longer than its stub); resolvePermissionSetsForContext, the memo, because a trial cut made the tenant-audit census exit 1 on its this.permissionSetMemo.delete() read through a host ([untyped-receiver], asking for a row in the shrink-only UNTYPED_RECEIVERS ledger); and describePositionGrants, because its body calls canReadObject and the core row-serving-door-exposure pin classifies every caller file. Module-level: readUnlessRefused (shared with absentUnderCallerRead and unreadableUnderCallerRead) and POSITION_NAME_FOLD_EVENT (linked in place by the staying reportNameFoldCollisions docblock) stay and gain export, imported back by value; the modules also import isGuestEnvelope, PREDICATE_WRITE_READABLE_ID_CAP, predicateWriteReadableCapError and copyOfCallerPredicate, already exported. Import names only the moved bodies read leave security-plugin.ts for the modules (MCP_AGENT_PERMISSION_SET_RESTRICTED, whose whole spec/ai import goes; readDisabledCatalogNames and withoutOperationPrivateKeys from core; resolveEngineDeleteDispatch and EngineDeleteDispatchInput from metadata-core). A local trial build checked the cut 6 export trap before the claim. No reader outside the moved code reads the moved lines by path, so only the three source files change. scripts/adr-anchors and docs/adr untouched. Published d.ts byte-identical to base. No open question.",
    "tests": "pnpm --filter @objectstack/plugin-security test (vitest run --maxWorkers=3, under os-verify-lock) on 23b0616: Test Files 214 passed (214), Tests 4465 passed | 45 skipped (4510); VERDICT command-exit 0. Base 2dfeb1a: the same counts, VERDICT command-exit 0. packages/core row-serving-door-exposure.pin.test.ts and operation-private-keys.pin.test.ts 8 of 8. Move-only, so no ablation. typecheck exit 0 (tsc --noEmit, the scripts tsconfig, check:test-typecheck). eslint --no-inline-config over the three source files exit 0. Package build exit 0, after a build of the package dependencies on 2dfeb1a. Move proof: each moved body has the same TypeScript AST token sequence as the base body and each of its 217 lines is the base line minus two leading spaces; parameters, return types and async match. Three negative controls fail the check (resolvePermissionSetsForContextUnmemoized not-isAgent changed to isAgent; readableRowsScopeForPredicateWrite PREDICATE_WRITE_READABLE_ID_CAP plus 1 changed to plus 2; reportNameFoldCollisions removedIn next_major changed to next_minor). Class: each moved method keeps docblock and signature less async; every other member (117 named plus the constructor; 127 at base and head) byte-identical, the seven staying members included. Top level of security-plugin.ts: 97 of 103 base statements byte-identical; the six others are readUnlessRefused and POSITION_NAME_FOLD_EVENT (base text plus export), three imports that lose the names above (@objectstack/core twice, @objectstack/metadata-core) and the removed @objectstack/spec/ai import; two new statements, the module imports. git diff --color-moved=plain --color-moved-ws=allow-indentation-change 2dfeb1a 23b0616 over the three source files: 218 of 227 deleted and 229 of 578 added lines moved; the 9 deleted-not-moved are the four signatures that lose async, the two declarations that gain export and the three import lines that lose names. 805 changed lines, 3 files; security-plugin.ts 9,394 to 9,248, the new modules 281 and 216.",
    "dts_block": {
    "files": "packages/plugins/plugin-security dist/index.d.ts and dist/index.d.mts, base 2dfeb1a and head 23b0616, both built against 2dfeb1a dependencies",
    "raw_sha256": "base index.d.ts af5dee1bebafa0376a876d1e7b247c878ea42ac35c7336a6d3c12a7953143156 · head index.d.ts af5dee1bebafa0376a876d1e7b247c878ea42ac35c7336a6d3c12a7953143156 · base index.d.mts and head index.d.mts the same hash",
    "normalised_sha256": "base 431e7f5097ac1c220bc840188aef02105bc0b16e845e976dd562d02cc79be8bd · head 431e7f5097ac1c220bc840188aef02105bc0b16e845e976dd562d02cc79be8bd (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 1307, private resolvePermissionSetsForContextUnmemoized; made public: raw 19c84435e20b905ee3f3d43803961fa4f0475b04f5747e4662731d5c50432b45, normalised cb6bcc7126d1a176815c99a2b9ad5a674d7b9f5f7b1c0dd43b47e39c5980f8a6; renamed to resolvePermissionSetsForContextUnmemoizedRenamed: raw bbfe2a4f35bc7a2f415852a2bb4ba3f35bd78391557b484e8638dee38b1b4039, normalised 677731d933ae7a9ddf2e4c65135853065b58506a08d33b62dab1ed2d1a9fb974. Both move the normalised hash.",
    "exports": "ESM and CJS bundles loaded at runtime (require and import of the built bundles): 66 names each, base and head; sorted-list sha256 83576430f83cf60cecbcc5c0e80214f16c626a8e66f700294fc6a44361423116 on all four, as at cuts 6 and 7. The new exports of security-plugin.ts (readUnlessRefused, POSITION_NAME_FOLD_EVENT) do not reach the entry. Bundle bytes differ (index.js cf7a65c1 to 3a109ef0, index.mjs d4178511 to 1c38ee91): code moved within them. The removed spec/ai import now sits in the permission-sets segment (index.mjs line 9232), so the bundle first import of @objectstack/spec/ai is one it already had, at line 1046 (1047 at base), rather than line 664; @objectstack/spec declares sideEffects false, so no observable evaluation order moves.",
    "note": "The hashes equal the cut 6 and cut 7 reports. The two new exports were measured in a local trial build before the claim because of the cut 6 trap; the d.ts did not change by a byte. The nine moved methods are private; the d.ts carries each name and docblock only (lines 1261, 1307, 1326, 1448, 1542, 1574, 1600, 1601, 1643 of both)."
    },
    "adr_anchor_block": {
    "gate": "node scripts/check-adr-symbol-anchors.mjs — base 2dfeb1a exit 0, head 23b0616 exit 0; both print 2384 anchors across 142 records resolve, 466 symbol (440 declaration, 26 literal), 1890 file-level, 28 cross-repo, 6 exempt, 18 continuation. Not derived by dispatch-gates for this change set (#21697); run by hand (ruling item 2). On a scratch merge with origin/main 830a63a: exit 0, the same 2384.",
    "map": "Read BEFORE the claim on 2dfeb1a with the whole cut applied as a local trial: every ADR symbol anchor naming security-plugin.ts (27 anchors, 18 symbols), measured with extractAnchors and symbolSegmentResolution from scripts/symbol-anchors.mjs: no anchor changed class. At head all stay in security-plugin.ts with their base class and neither new module resolves any. Base line to head line, class: #CBP_MAX_CHAIN_DEPTH (ADR-0055:135) :665 to :688 declaration top-level; #SecurityPlugin (ADR-0049:49, ADR-0138:248, :265) :1297 to :1320 declaration; #SecurityPlugin.getMetadataReadableFields (ADR-0138:153) :6092 to :6115 declaration; #SecurityPlugin.resolvePermissionSetsForContextUnmemoized (ADR-0138:427) :7003 to :7020 declaration (the method stays with its docblock; its body moved); #assertControlledByParentWrite (ADR-0055:58, :125) :8651 to :8505 declaration; #assertMasterRowEditable (ADR-0055:125) :8777 to :8631 declaration; #collectRLSPolicies (ADR-0105:93) :8831 to :8685 declaration; #composeWriteTenantWall (ADR-0123:4) :7989 to :7843 declaration; #computeControlledByParentFilter (ADR-0055:5, :123, ADR-0139:246) :8297 to :8151 declaration; #computeDelegatedReadFilter (ADR-0139:223) :5970 to :5993 declaration; #computeRlsFilter (ADR-0055:24) :7697 to :7551 declaration; #getReadFilter (ADR-0096:181, :182, :183, :199, ADR-0139:222) :5848 to :5871 declaration; #hasPlatformAdminPosture (ADR-0099:317) :7724 to :7578 declaration; #isPrincipalLessContext (ADR-0138:152) :538 to :561 declaration top-level; #manage_metadata (ADR-0099:68) :287 to :310 literal; #nav_permission_sets (ADR-0126:406) :1725 to :1748 literal (init); #rlsFilter (ADR-0055:24) :4299 to :4322 declaration (local in start); #writeCheckPolicies (ADR-0066:112) :1192 to :1215 declaration top-level. No anchor gains or loses a resolving site. Every security-plugin.ts# anchor anywhere in the repo (30 symbols: spec liveness, platform checklist, system-context.mdx, spec conversions/registry.ts included) resolves at head with its base class: 0 changed.",
    "adr_anchors": "pnpm check:adr-anchors exit 0 at base and head, 80 anchored files at both (no entry added or changed). Disclosed count shift: decision-letter citations on the anchored surface 2005 at base to 2002 at head. 3 ADR-NNNN Dk citations move with the resolvePermissionSetsForContextUnmemoized body into the unanchored permission-sets module (ADR-0138 D3, ADR-0090 D10, ADR-0090 D5); none arrives (the two new import comments, the module headers and the pointer comments cite no decision letter). Files carrying citations 6631 to 6632 (the permission-sets module). No anchor entry invented for either module: no paragraph of the security-plugin.ts entry (ADR-0055 §1, ADR-0106 D7, ADR-0123 D2, ADR-0138 D1) describes these bodies, as cuts 6 and 7 disclosed the same kind of shift. Scratch merge with origin/main 830a63a: 80 files and 2002."
    },
    "gates": "Local on 23b0616: dispatch-gates --commands, 52 commands (no docs family: no page touched); each run with its exit captured before any pipe; --ran: 52 derived, 52 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero). Run by hand, not derived, all exit 0: check-adr-symbol-anchors, check-platform-checklist (and pnpm check:platform-checklist), check-scripts-symbol-anchors (and --self-test), pnpm check:adr-anchors, pnpm check:query-options-erasure, isystem-census --json, tenant-audit-census --json. Census gates base vs head: identical output for check-adr-symbol-anchors, check-platform-checklist, check-platform-object-tenancy-census, check-registry-log-declared, check-rest-log-spy-declared, check-system-context-census, check-tenant-audit-census, check:cross-package-test-inputs, check:driver-memory-census, check:query-options-erasure. isystem-census --json: 148 sites at both, 0 differences with line numbers dropped and the new modules mapped back, no site in either module; role counts equal (read 153, declaration 38, key 438, other 9), 5 non-elevation reads at both; scanned files 393 at both. tenant-audit-census --json (new modules tracked with git add -N for the local runs, since the census scans tracked files): 238 write sites at both, 0 differences, the same 174 non-engine subtractions with the same reasons, 0 unresolved, 0 unledgered; scannedSources 721 to 723. Only scanned counts moved otherwise, each by the two new files (three where a head-only local build output or generated page exists): check-undeclared-dep-imports 3307 to 3309 files and 2853 to 2857 specifiers (spec/security, spec/ai, core, metadata-core, all declared), check:org-identifier, check:page-declaration-shape, check:logger-receiver-detach, check:refd-timer-probe, check-plugin-teardown-shape, check-comment-mask-corpus, check:closed-ceremony-text, check:nul-bytes, check:gitlink-declared, check-closing-keyword-parity, check:error-status-conformance, spec check:duration-unit-keys, check:doc-authoring (1426 to 1428 sources), check-position-name-fold-loaders (12343 to 12348 files, the same 8 references). check:dual-build-cjs-loads and check:i18n answer exit 3 at base (missing-build prerequisite in the base worktree, not a verdict) and exit 0 at head. check-issue-citations judges the change set: 26 citations across 3 files, all resolve. Docs Drift Check re-derived with node scripts/docs-audit/affected-docs.mjs --json 2dfeb1a: 0 pages (29 anchors read, none named by a page); the bot comment 6364161929, computed on the merge with 830a63a, gives the same 0 from the same 29 anchors. Scratch merge of this head with origin/main 830a63a (#25402, touching none of this cut files): clean; check-adr-symbol-anchors, check:adr-anchors, check-platform-checklist, check-system-context-census and check-tenant-audit-census exit 0 there. CI on head 23b0616, all completed, none failed: Auto Label success (a second run of the same name skipped by its filter), Build Core success, Check Changeset success (a second run of the same name skipped by its filter), Check Documentation Links success, Check PR Size success (a second run of the same name skipped by its filter), Dogfood Regression Gate success, filter success, Flag docs affected by code changes success, Governed Surface Queue Guard success, Lint · CI, release & engine gates success, Lint · docs & code guards success, Lint · ESLint success, Lint · PM dispatch-gates success, Lint · repo & tooling gates success, Lint · scoped · query-options erasure success, Lint · scoped · slot lookup & corpus success, Lint · scoped · tooling self-tests success, Lint & Repo Gates 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, Test 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, The card this PR closes must claim this branch success, Type Check · consumer gates success, Type Check · debt ledger success, Type Check · source gates success, Type Check · workspace success, TypeScript Type Check success; skipped by their own filters: 18.x upgrade harness (opt-in), Build Docs, Console Pin Gate, the sharded Dogfood Regression Gate matrix, Dogfood Verify CLI, Packed-tarball smoke (opt-in), Temporal Conformance (live PG + MySQL). PR mergeable_state clean.",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "none in this cut — every GitHub read was gh api GET (card comments, open PR files for the serial check, PR state, check runs). Also claude-code-remote subscribe_pr_activity on #25406 (a session subscription, not a GitHub write). No MCP write tool.",
    "api_writes": "5 strokes, all through the scripts/pm relay or git: (1) post-stamped claim comment 6363282332 on #25223 (relay run 39942433795); (2) git push of claude/project-thread-3fzya4 to 23b0616 (new branch from 2dfeb1a; the merged branch had been deleted by the queue); (3) fleet-write pr_create, PR #25406 draft with assignee os-sales and label skip-changeset (relay run 39948432355; body read back identical, 34092 bytes, ending with both footers); (4) fleet-write pr_request_reviewers os-sales (relay run 39948517917); (5) this report, via post-stamped.",
    "open_questions": [],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  13. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed cut 8 (plan row 6) · domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · 2026-10-23T13:04Z

    reading value
    PR #25406 MERGED through the queue (the seat's ACCEPT is on the PR), head 23b0616bae
    origin/main carries 25f88e899a

    State: the card stays open and dispatched (Part of). The card thread claims plan row 7 (read filters, field readers, object admission) afresh on the merged file.


    Generated by Claude Code

  14. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p2 · series card, cut 9 of the security-plugin.ts split: plan row 7, the read filters, field readers and object admission, move out · 2026-10-23T13:27Z
    Ruling-ref: 6353073966 (item 2 for the anchor reading below); the series' .d.ts bar stays ruling 6348891160 item 1
    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, restarted from origin/main at 25f88e899a, cut 8's merge. It names no issue, so this claim is the identity record.
    Worktree: objectstack-issue-25223
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)

    Why now: the landing record 6365547058 hands the card thread plan row 7, claimed afresh on the merged file. The plan in the cut 1 claim 6349703046 lists it as read filters, field readers, object admission (getReadFilter .. canExport). No start() code is in this cut.

    File surface, cut 9 (stop on a breach and explain it in the report). Row 7 has three seams, so the bodies go to THREE new modules in this one PR, as cuts 6 and 8 split theirs over two.

    • packages/plugins/plugin-security/src/security-plugin.ts, at 25f88e899a:
      • To security-plugin-read-filter.ts (host ReadFilterHost), the bodies of two methods:
        • getReadFilter (public, :5871-:5954), the row-level scope the service serves to the doors that compile their own statement.
        • computeDelegatedReadFilter (:5993-:6055), the same scope for an on-behalf-of caller, the two legs intersected.
      • To security-plugin-field-readers.ts (host FieldReadersHost), the bodies of three private methods:
        • resolveProjectionFieldMask (:6207-:6300), the derivation every field projection shares.
        • computeReadableFields (:6119-:6140), the readable projection both planes answer from.
        • resolveCallerPosture (:6346-:6365), the one posture a caller's gates read.
      • To security-plugin-object-admission.ts (host ObjectAdmissionHost), the bodies of four methods:
      • Each method stays in the class with its name, signature, docblock and position, less async, and hands its body one host literal of exactly the members that body reads. Methods go as late-bound closures. Every field the bodies read is not declared readonly (delegatedAdminGate, logger, metadata, permissionEvaluator, ql, tenancyPosture), so all go as getters. 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 cuts 1 to 8 applied it.
      • Four members of the row stay in the class whole: getReadableFields, getMetadataReadableFields, getWritableFields and getQueryableFields. Each body is no longer than its stub would be.
      • Module-level declarations:
        • EMPTY_REQUIRED_PERMISSIONS (:523-:525) is read only by resolveCallerPosture's body, so it moves with it, unedited, as cuts 1 to 4 moved the declarations only their bodies read. Its type, NormalizedRequiredPermissions, stays unexported in security-plugin.ts; the module names it through the class's own signature (the posture's requiredPermissions field), as cut 1's module names ObjectSecurityMeta.
        • The import name intersectFieldMasks (./explain-engine.js) is read only by resolveProjectionFieldMask's body, so it leaves security-plugin.ts's import, and the field readers import it from the same specifier. No staying docblock links to it; canWriteObject's docblock names it in backticks only.
        • isPrincipalLessContext (cut 6) and requiredCapsForOperation are already exported, and the modules import them by value. security-plugin.ts gains no new export, so the cut 6 .d.ts trap does not arise. A local trial build with the whole cut in place produced a head dist/index.d.ts and index.d.mts byte-identical to base (7d67966c…, on 25f88e899a's dependencies).
    • Two readers outside the moved code read the moved lines by path, and both are re-pointed in this PR, as cut 6 re-pointed the same two:
      • content/docs/permissions/system-context.mdx, rows 3, 4, 5, 6 and 6b. check-system-context-census keys each elevation read by file and enclosing symbol, and the five context?.isSystem reads now sit in the new modules. A trial measured it: with the page untouched the census reports 8 problems (3 files without a row, 5 anchors that are no longer read sites). Each anchor's path changes from security-plugin.ts#X to the new module's #X; the symbol, the row text and every other anchor stay. With that, the census exits 0: 148 sites, 140 cited symbols against 140 required, 163 anchors, files 71 to 74.
      • packages/plugins/plugin-security/src/delegated-organization-membership.test.ts, the plugin-security: delegation probes and diagnostics (canReadObject / canWriteObject / canExport, field mask, explain) answer for an on-behalf-of context the operation prologue now refuses (PR #25101) #25107 enumeration pin. It lists the files that resolve a delegator, and the five moved bodies that do so (computeDelegatedReadFilter, resolveProjectionFieldMask, canReadObject, canWriteObject, canExport) each ask the organization check beside their resolution. The expected list gains the three modules, and the title says six files instead of three. The rule it pins, asks equal resolutions in every file, is unchanged, and it holds at the trial head: read filter 1 and 1, field readers 1 and 1, object admission 3 and 3, security-plugin.ts 2 and 2.
    • scripts/adr-anchors/: no change. No paragraph of the security-plugin.ts entry describes these bodies, so no new module gets an entry, as the earlier modules have none. check:adr-anchors counts decision letters only on the anchored surface. The moved bodies carry 27, and the new import comments carry none, so the count goes from 2001 at base to 1974 at head. That is disclosed, and no anchor entry is invented for it:
      • getReadFilter 4 (ADR-0056 D2, ADR-0090 D10, ADR-0096 D5, ADR-0139 D5); computeDelegatedReadFilter 4 (ADR-0090 D10 three times, ADR-0123 D2);
      • resolveProjectionFieldMask 3 (ADR-0090 D10, ADR-0096 D5, ADR-0106 D7);
      • canReadObject 3 and canExport 3 (each ADR-0056 D2, ADR-0090 D10, ADR-0096 D5); objectReadAdmission 2 (ADR-0066 D3, ADR-0090 D10); canWriteObject 8 (ADR-0056 D2, ADR-0066 D3, ADR-0090 D10 twice, ADR-0090 D12, ADR-0096 D5, ADR-0123 D2 twice).
    • docs/adr/**: not touched.
    • Every other 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. None other was found (below).
    • .d.ts: ruling 6348891160 item 1, with the same four-hash block, normaliser, raw line count and positive control as cuts 1 to 8.
    • No changeset (skip-changeset).
    • ⛔ Not touched: the four staying projections, every other section of the class, the row 8 members (declareEveryone .. checkAuthoredRowWrite), start(), and any behaviour.

    Anchor and reader reading before the cut (ruling item 2), on 25f88e899a, with the trial cut in place rather than blanked bodies:

    • ADR anchors: every ADR symbol anchor into security-plugin.ts was mapped with extractAnchors and symbolSegmentResolution from scripts/symbol-anchors.mjs. That is 27 anchors over 18 symbols, and none changes class at the trial head. The row's three anchored names, #getReadFilter (ADR-0096:181, :182, :183, :199, ADR-0139:222), #computeDelegatedReadFilter (ADR-0139:223) and #SecurityPlugin.getMetadataReadableFields (ADR-0138:153), resolve on the methods, which stay with their docblocks.
    • node scripts/check-adr-symbol-anchors.mjs exits 0 at base and at the trial head, with the same line: 2384 anchors, 440 declaration-class and 26 literal-class.
    • The spec liveness ledger's security-plugin.ts#canExport evidence (permission.json, allowExport) resolves on the method, which stays, and allowExport is still named in security-plugin.ts (in canExport's docblock), so its key-mention reading does not change. pnpm --filter @objectstack/spec check:liveness gives byte-identical output at base and at the trial head. The entry's prose, canExport asking checkObjectPermission('export'), stays true by method: the body is one call away. packages/spec/** is not touched.
    • Censuses, with the three modules tracked (git add -N), compared site by site with the module paths mapped back:
      • isystem-census: 148 sites at both ends, 0 differences; the five moved reads sit in the modules.
      • tenant-audit-census: 238 sites, 0 differences, 174 non-engine subtractions at both ends; no moved body holds a write verb. check-tenant-audit-census exits 0.
    • Readers by path:
      • row-serving-door-exposure.pin.test.ts and operation-private-keys.pin.test.ts (@objectstack/core) pass at the trial head, 8 of 8. The pin classifies callers of .canReadObject(; no moved body calls it (the canReadObject body is the definition), and security-plugin.ts still calls it, from describePositionGrants and the service binding.
      • check-platform-checklist and check-scripts-symbol-anchors give the same output at base and at the trial head.
      • No security-plugin.ts:N line citation points into the moved ranges.
      • No test replaces or spies on a moved method on the plugin instance.

    Serial check: of the 24 open PRs, none touches security-plugin.ts, the new module names, system-context.mdx, delegated-organization-membership.test.ts, the census scripts, the liveness ledger or the plugin-security anchor entry. #25389 and #25410 edit the scan scope of the core read-admission pin (an extension-scoped prefilter), not what it discriminates on.

    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 10 starts.
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: 6365547058
    Resume-from: none


    Generated by Claude Code

  15. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim amendment: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · cut 9 of the security-plugin.ts split · 2026-10-23T13:33Z
    Amends: claim 6365993271
    Ruling-ref: 6353073966 for the anchor reading, unchanged; 6348891160 (item 1, the series' .d.ts bar)
    Session: session_017BoLT8Gaa8t9a7AWVH8qZa
    Seat: domain:services#1 (seat post #6021)

    One line of the file surface changes, before any push. The claim had scripts/adr-anchors/ unchanged. It gains one entry instead: scripts/adr-anchors/packages__plugins__plugin-security__src__security-plugin-field-readers.ts.json. The entry carries ADR-0106 and the ADR-0106 D7 invariant paragraph of the security-plugin.ts entry, verbatim, as the entries of cuts 2 to 5 carry theirs.

    Why: re-read while writing the report. The claim said no paragraph of the security-plugin.ts entry describes the moved bodies, and for one paragraph that is wrong. The ADR-0106 D7 paragraph names getMetadataReadableFields and getReadableFields, which stay. But its metadata-plane half, "the same caller resolves the configured fallback permission set", is the fallbackOnEmptySets branch of resolveProjectionFieldMask, whose body moves to the field-readers module. The body still carries its [ADR-0106 D7] comment there. Cut 4 set the rule for this: its module got an entry because one half of the ADR-0055 §1 paragraph ("reads answer RLS_DENY_FILTER") was the leaf it moved. The security-plugin.ts entry is unchanged, because the two methods, their docblocks and the flag each passes stay in that file.

    What this moves, disclosed:

    • pnpm check:adr-anchors counts 81 anchored files at head instead of 80.
    • The decision-letter count on the anchored surface goes from 2001 at base to 1977 at head, not 1974. The 3 letters in resolveProjectionFieldMask's body (ADR-0090 D10, ADR-0096 D5, ADR-0106 D7) are on the anchored surface again, in the field-readers module. The 24 in the read-filter and object-admission bodies leave it.
    • No anchor token is planted, and no docs/adr/** file is touched.

    Every other line of the claim stands. No paragraph of the entry describes the read-filter or object-admission bodies:

    • ADR-0055 §1 is the controlled-by-parent relation, which stays in its cut 2 to 4 modules.
    • ADR-0123 D2 is the write wall's no-organization refusal, in its cut 5 module. canWriteObject only asks that wall's scope pre-test, as the middleware does.
    • ADR-0138 D1 is the seed-ownership claim.

    So neither of those two modules gets an entry.


    Generated by Claude Code

  16. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 25223,
    "status": "done",
    "branch": "claude/project-thread-3fzya4",
    "pr": "#25456",
    "session": "session_017BoLT8Gaa8t9a7AWVH8qZa — this run's harness-stamped id, on the Claude-Session: trailer of every commit on the branch",
    "premise_still_valid": true,
    "summary": "Cut 9 of the security-plugin.ts split, row 7 of the cut 1 plan (read filters, field readers, object admission); claim 6365993271, amended 6366124111 before any push (Ruling-ref 6353073966; d.ts bar 6348891160). The row has three seams, so the bodies of nine methods moved unedited to three new modules in this one PR. To src/security-plugin-read-filter.ts (host ReadFilterHost): getReadFilter (public) and computeDelegatedReadFilter (ADR-0090 D10, #25106). To src/security-plugin-field-readers.ts (host FieldReadersHost): resolveProjectionFieldMask (#25107), computeReadableFields (#8993) and resolveCallerPosture (#20995, #21063). To src/security-plugin-object-admission.ts (host ObjectAdmissionHost): canReadObject (public), objectReadAdmission, canWriteObject (public, #18682) and canExport (public, #3544). Shape B as in cuts 1 to 8: each method keeps name, signature, docblock and position less async and hands its body one host literal typed as the Pick of its host over exactly the members it reads; methods as late-bound closures; delegatedAdminGate, logger, metadata, permissionEvaluator, ql and tenancyPosture (none declared readonly) as getters. Four members of the row stay whole (getReadableFields, getMetadataReadableFields, getWritableFields, getQueryableFields; each body no longer than its stub). Module-level: EMPTY_REQUIRED_PERMISSIONS, read only by resolveCallerPosture, moved byte-identical with it; its type is named in the module through the class signature (ObjectSecurityMeta as getObjectSecurityMeta resolved return, NormalizedRequiredPermissions as its requiredPermissions field), so security-plugin.ts gains no export. The import name intersectFieldMasks leaves security-plugin.ts for the field readers. The modules import isPrincipalLessContext and requiredCapsForOperation, already exported. Two path readers re-pointed in the same PR, as cut 6 did: five system-context.mdx anchors (rows 3, 4, 5, 6, 6b; path only, symbol and row text unchanged) and the #25107 enumeration pin expected file list (three modules added; title three to six files; asks equal resolutions in every file at head). One ADR anchor entry added (amendment 6366124111): the field-readers module carries ADR-0106 and the ADR-0106 D7 paragraph of the security-plugin.ts entry verbatim, because that paragraph metadata-plane half is the fallbackOnEmptySets branch of resolveProjectionFieldMask. docs/adr untouched, no token planted. Published d.ts byte-identical to base. No open question.",
    "tests": "pnpm --filter @objectstack/plugin-security test (vitest run --maxWorkers=3, under os-verify-lock) on the tree of 0feb79c (the second commit b4ced7f adds only the anchor entry JSON, which no package run reads): Test Files 214 passed (214), Tests 4465 passed | 45 skipped (4510); VERDICT command-exit 0. Base 25f88e8: the same counts, VERDICT command-exit 0. packages/core row-serving-door-exposure.pin.test.ts and operation-private-keys.pin.test.ts 8 of 8. Move-only, so no ablation. typecheck exit 0 (tsc --noEmit, the scripts tsconfig, check:test-typecheck). eslint --no-inline-config over the five touched source and test files exit 0. Package build exit 0, after a build of the package dependencies on 25f88e8; the full turbo run build at b4ced7f produced the same four dist files byte for byte. Move proof: each moved body has the same TypeScript AST token sequence as the base body and each of the 526 body lines is the base line minus two leading spaces; parameters, return types and async match; resolveProjectionFieldMask multi-line return type repeated one level in, 82 tokens equal. Three negative controls fail the check (getReadFilter isSystem negated; resolveProjectionFieldMask fields allFields changed to []; canExport export changed to read). Class: each moved method keeps docblock and signature less async; every other member (117 named plus the constructor; 127 at base and head) byte-identical, the four staying members included. Top level of security-plugin.ts: 102 of 104 base statements byte-identical; the two others are the explain-engine import (loses intersectFieldMasks) and EMPTY_REQUIRED_PERMISSIONS (now in the field-readers module, byte for byte); three new statements, the module imports. git diff --color-moved=plain --color-moved-ws=allow-indentation-change 25f88e8 b4ced7f over the four source files: 528 of 538 deleted and 561 of 1044 added lines moved; the 10 deleted-not-moved are the nine signatures that lose async and the import line intersectFieldMasks. 1610 changed lines (+1065 / -545), 7 files; security-plugin.ts 9,248 to 8,852, the new modules 241, 252 and 409.",
    "dts_block": {
    "files": "packages/plugins/plugin-security dist/index.d.ts and dist/index.d.mts, base 25f88e8 and head b4ced7f, both built against 25f88e8 dependencies",
    "raw_sha256": "base index.d.ts 7d67966ce82e00a1531807b3b0008f16c489fc2c3199f9ad67078302eb2803da · head index.d.ts 7d67966ce82e00a1531807b3b0008f16c489fc2c3199f9ad67078302eb2803da · base index.d.mts and head index.d.mts the same hash",
    "normalised_sha256": "base b53e9025f794e20debd2245afed5ceb1a0ad48109a2a21cc19232a562ccd9cb4 · head b53e9025f794e20debd2245afed5ceb1a0ad48109a2a21cc19232a562ccd9cb4 (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 839, private computeDelegatedReadFilter; made public: raw b46fd6c3c1ffaf2ede883a72a5d6fee1c692d38ab4d504639667880a0236ac09, normalised cadb75e96b0419b7bddb047925e7e3e8fab28abbed6e5ab161bd8f2a0d8a2bf6; renamed to computeDelegatedReadFilterRenamed: raw 1a5427bc1e748676c425f150b4e402dc29823002fe9e10389c7720c1106367dc, normalised 365336182c2d65b2021c3844251cc211787143f0fdbcb60af1418ed1e8daf73a; line 1246, the public canExport signature, context any changed to unknown: raw f0088fc10e9713f29fc84a6ec7fde84f3b5005556ca001ceb7bae565f20d6ab0, normalised f8bfba0031d62ff891175079526ff10f4017ffd0cf7148c61e220d1092777115. All three move the normalised hash.",
    "exports": "ESM and CJS bundles loaded at runtime (require and import of the built bundles): 66 names each, base and head; sorted-list sha256 83576430f83cf60cecbcc5c0e80214f16c626a8e66f700294fc6a44361423116 on all four, as at cuts 6 to 8. Bundle bytes differ (index.js 3a109ef0 to dfa83a54, index.mjs 1c38ee91 to 18c4d919): code moved within them. Every value the modules import is a file inside the package, so the distinct external specifiers are the same at base and head (16 require in index.js, 17 from in index.mjs).",
    "note": "The hashes differ from cuts 6 to 8 because main moved under the series: #24947 widened the spec bulk-action confirmText, inlined in three places; the diff between cut 8 head d.ts and this base is exactly those three hunks. security-plugin.ts gains no export. The four public methods carry full signatures (getReadFilter line 801, canReadObject 1045, canWriteObject 1227, canExport 1246); the five private ones name and docblock only (839, 896, 950, 995, 1080); those lines are the same in both files."
    },
    "adr_anchor_block": {
    "gate": "node scripts/check-adr-symbol-anchors.mjs — base 25f88e8 exit 0, head b4ced7f exit 0; both print 2384 anchors across 142 records resolve, 466 symbol (440 declaration, 26 literal), 1890 file-level, 28 cross-repo, 6 exempt, 18 continuation. Not derived by dispatch-gates for this change set (#21697); run by hand (ruling item 2). On a scratch merge with origin/main 2581efb: exit 0, the same 2384.",
    "map": "Read BEFORE the claim on 25f88e8 with the whole cut applied as a local trial: every ADR symbol anchor naming security-plugin.ts (27 anchors, 18 symbols), measured with extractAnchors and symbolSegmentResolution from scripts/symbol-anchors.mjs: no anchor changed class. At head all stay in security-plugin.ts with their base class. Base line to head line, class: #CBP_MAX_CHAIN_DEPTH (ADR-0055:135) :688 to :712 declaration top-level; #SecurityPlugin (ADR-0049:49, ADR-0138:248, :265) :1320 to :1344 declaration; #SecurityPlugin.getMetadataReadableFields (ADR-0138:153) :6115 to :6030 declaration; #SecurityPlugin.resolvePermissionSetsForContextUnmemoized (ADR-0138:427) :7020 to :6624 declaration; #assertControlledByParentWrite (ADR-0055:58, :125) :8505 to :8109 declaration; #assertMasterRowEditable (ADR-0055:125) :8631 to :8235 declaration; #collectRLSPolicies (ADR-0105:93) :8685 to :8289 declaration; #composeWriteTenantWall (ADR-0123:4) :7843 to :7447 declaration; #computeControlledByParentFilter (ADR-0055:5, :123, ADR-0139:246) :8151 to :7755 declaration; #computeDelegatedReadFilter (ADR-0139:223) :5993 to :5949 declaration (the method stays with its docblock; its body moved); #computeRlsFilter (ADR-0055:24) :7551 to :7155 declaration; #getReadFilter (ADR-0096:181, :182, :183, :199, ADR-0139:222) :5871 to :5895 declaration (likewise); #hasPlatformAdminPosture (ADR-0099:317) :7578 to :7182 declaration; #isPrincipalLessContext (ADR-0138:152) :561 to :585 declaration top-level; #manage_metadata (ADR-0099:68) :310 to :338 literal; #nav_permission_sets (ADR-0126:406) :1748 to :1772 literal (init); #rlsFilter (ADR-0055:24) :4322 to :4346 declaration (local in start); #writeCheckPolicies (ADR-0066:112) :1215 to :1239 declaration top-level. Non-ADR corpora (spec liveness, platform checklist, system-context.mdx, spec conversions/registry.ts): 27 anchors over 17 symbols naming security-plugin.ts, 0 changed. Every path-qualified plugin-security security-plugin*.ts anchor outside docs/adr: 32 at base and head, 0 unresolved; at head 23 in security-plugin.ts, the five re-pointed in the new modules (1, 1, 3), 4 in the cut 3 and cut 5 modules.",
    "adr_anchors": "pnpm check:adr-anchors exit 0 at base and head; anchored files 80 to 81 (the new field-readers entry). Disclosed count shift: decision-letter citations on the anchored surface 2001 at base to 1978 at head. 24 leave with the read-filter and object-admission bodies (getReadFilter 4: ADR-0056 D2, ADR-0090 D10, ADR-0096 D5, ADR-0139 D5; computeDelegatedReadFilter 4: ADR-0090 D10 x3, ADR-0123 D2; canReadObject 3 and canExport 3: ADR-0056 D2, ADR-0090 D10, ADR-0096 D5 each; objectReadAdmission 2: ADR-0066 D3, ADR-0090 D10; canWriteObject 8: ADR-0056 D2, ADR-0066 D3, ADR-0090 D10 x2, ADR-0090 D12, ADR-0096 D5, ADR-0123 D2 x2). resolveProjectionFieldMask 3 (ADR-0090 D10, ADR-0096 D5, ADR-0106 D7) stay on the anchored surface in the field-readers module. One arrives: the new entry invariant names ADR-0106 D7 once, and the gate counts entry text. The amendment 6366124111 said 1977 because it left that one out; measured 1978. Tree-wide resolving citations 52175 across 6636 files to 52179 across 6640. No entry for the read-filter or object-admission modules: no paragraph of the security-plugin.ts entry (ADR-0055 §1, ADR-0123 D2, ADR-0138 D1) describes those bodies. Scratch merge with origin/main 2581efb: 81 files and 1978."
    },
    "gates": "Local on b4ced7f: dispatch-gates --commands, 98 commands (the docs family is derived: the PR touches a page); each run with its exit captured before any pipe; --ran: 98 derived, 98 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero). Run by hand, not derived, all exit 0: check-adr-symbol-anchors, check-platform-checklist (and pnpm check:platform-checklist), pnpm --filter @objectstack/spec check:liveness, isystem-census --json, tenant-audit-census --json. The six artifact-roster gates whose roster sits under a touched directory, run by hand at base and head, all exit 0 with identical output (check-error-code-casing scans +3): check-published-list-mirrors, check:authz-resolver, check:console-injection, check:error-code-casing, check:filter-alias-parity, check:published-readme-exports. All 104 commands exit 0 at head; 63 give identical output at base and head (path and timings masked), among them check-adr-symbol-anchors, check-platform-checklist, check-scripts-symbol-anchors, check-platform-object-tenancy-census, check-tenant-audit-census, check-registry-log-declared, check-rest-log-spy-declared, check:cross-package-test-inputs, check:driver-memory-census, check:query-options-erasure and spec check:liveness. isystem-census --json: 148 sites at both, 0 differences with line numbers dropped and the modules mapped back; 5 head sites in the modules (the five re-pointed anchors); role counts equal (read 153, declaration 38, key 438, other 9), 5 non-elevation reads; files 71 to 74, scanned files 393 to 396. check-system-context-census exit 0 at both, files 71 to 74 (with the page untouched a trial reported 8 problems). tenant-audit-census --json: 238 write sites at both, 0 differences, none in the modules, the same 174 non-engine subtractions, 0 unresolved, 0 unledgered; scannedSources 724 to 727. The other differences, each read: scanned counts +3 for the modules (check-plugin-teardown-shape, check-undeclared-dep-imports 3310 to 3313 files and 2859 to 2862 specifiers, all spec subpaths, check:doc-formula-expressions, spec check:duration-unit-keys, check:closed-ceremony-text, check:engine-double-contract, check:error-status-conformance, check:org-identifier, check:refd-timer-probe, check:type-check-coverage and -debt, check:skill-identifier-liveness tokens); +4 with the gitignored apps/docs/next-env.d.ts in the head worktree (check-comment-mask-corpus, check:logger-receiver-detach, check:page-declaration-shape); tracked-file counts +4 with the entry (check-closing-keyword-parity, check:gitlink-declared, check:nul-bytes; check:pnpm-flag-spellings and check:agent-test-spelling read the entry under scripts/); docs counts from three gitignored generated protocol-upgrade pages present only in the head worktree (check-docs-section-name, check:corpus-claim-drift, check:doc-anchors, check:doc-authoring, check:react-page-adapter-contract, check-doc-route-spelling advisory, check:vendor-version-stamps whose 7 extra better-auth stamps are all in the generated 18.md); check-position-name-fold-loaders walks the working tree (the same 8 references); check:dts-closure and check:sourcemap-no-sources-content sweep more built packages at head (76, 72) than base (19), both pass at both; check:merge-driver differs only in temp directory names. Five exit non-zero at base only, each a missing or stale base-worktree build rather than a verdict: spec check:docs (exit 1, generated schema absent), check:skill-examples, check:dual-build-cjs-loads, check:i18n (exit 3, prerequisite not met), check:docs-transcript-drift (exit 1: it reads the built lint registry, and the base build predated #24947 added rule; after that package was rebuilt in the same run, a rerun at base exits 0 with the head output). check-issue-citations judges the change set: 56 citations across 4 files, all resolve. Docs Drift Check re-derived with node scripts/docs-audit/affected-docs.mjs --json 25f88e8: 41 pages, advisory, all reached through names the move relocated (requiredPermissions 29 via the field readers type alias, permissionSets 10, SecurityPlugin 6, the moved method names getReadFilter 4, canReadObject 3, canExport 2, canWriteObject 2, resolveProjectionFieldMask 1); each member keeps name, signature, docblock and behaviour, and the one page that reads the moved lines by path (system-context.mdx) is re-pointed here. The bot comment 6367617451, computed on the merge with 6c2699b, gives the same 41 pages (31 hand-written, 10 release-owned) from the same 66 anchors. Scratch merge of this head with origin/main 2581efb (13 commits, touching none of this cut files): clean; check-adr-symbol-anchors, check:adr-anchors, check-platform-checklist, check-system-context-census, check-tenant-audit-census and isystem-census exit 0 there. CI on head b4ced7f, all completed, none failed: Auto Label success (a second run of the same name skipped by its filter), Build Core success, Build Docs success, Check Changeset success (a second run of the same name skipped by its filter), Check Documentation Links success, Check PR Size success (a second run of the same name skipped by its filter), Dogfood Regression Gate success, filter success, Flag docs affected by code changes success, Governed Surface Queue Guard success, Lint · CI, release & engine gates success, Lint · docs & code guards success, Lint · ESLint success, Lint · PM dispatch-gates success, Lint · repo & tooling gates success, Lint · scoped · query-options erasure success, Lint · scoped · slot lookup & corpus success, Lint · scoped · tooling self-tests success, Lint & Repo Gates 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, Spec property liveness success, Test 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, The card this PR closes must claim this branch success, Type Check · consumer gates success, Type Check · debt ledger success, Type Check · source gates success, Type Check · workspace success, TypeScript Type Check success; skipped by their own filters: 18.x upgrade harness (opt-in), Console Pin Gate, the sharded Dogfood Regression Gate matrix, Dogfood Verify CLI, Packed-tarball smoke (opt-in), Temporal Conformance (live PG + MySQL). PR mergeable_state clean.",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "none in this cut — every GitHub read was gh api GET (card comments, open PR files for the serial check, PR state, check runs). Also claude-code-remote subscribe_pr_activity on #25456 (a session subscription, not a GitHub write). No MCP write tool.",
    "api_writes": "6 strokes, all through the scripts/pm relay or git: (1) post-stamped claim comment 6365993271 on #25223 (relay run 39961389521); (2) post-stamped claim amendment 6366124111 on #25223 (relay run 39962270481); (3) git push of claude/project-thread-3fzya4 to b4ced7f (new branch from 25f88e8; the merged branch had been deleted by the queue); (4) fleet-write pr_create, PR #25456 draft with assignee os-sales and label skip-changeset (relay run 39972330995; body read back identical, 44874 bytes, ending with both footers); (5) fleet-write pr_request_reviewers os-sales (relay run 39972460011); (6) this report, via post-stamped.",
    "open_questions": [],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  17. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed cut 9 (plan row 7) · domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · 2026-10-23T15:56Z

    reading value
    PR #25456 MERGED through the queue (the seat's ACCEPT is on the PR), head b4ced7f87e
    origin/main carries dd124e9466

    State: the card stays open and dispatched (Part of). Next, the card thread claims plan row 8 (declareEveryone..checkAuthoredRowWrite) afresh on the merged file. start() is Tier H and goes to the seat before any PR.


    Generated by Claude Code

  18. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p2 · series card, cut 10 of the security-plugin.ts split: plan row 8, the everyone anchor, the explanation, the sharing verdicts and the authored-row write verdict, move out · 2026-10-23T16:26Z
    Ruling-ref: 6353073966 (item 2 for the anchor reading below); the series' .d.ts bar stays ruling 6348891160 item 1
    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, restarted from origin/main at 05cc3ee953, which contains cut 9's merge dd124e9466. It names no issue, so this claim is the identity record.
    Worktree: objectstack-issue-25223
    Domain: domain:services (the seat post's name; #25247 aliases it to domain:server)
    Seat: domain:services#1 (seat post #6021)

    Why now: the seat's landing note for cut 9 hands the card thread plan row 8, claimed afresh on the merged file. The plan in the cut 1 claim 6349703046 lists it as declareEveryone .. checkAuthoredRowWrite. No start() code is in this cut; start() is Tier H and goes to the seat with its scope before any PR.

    File surface, cut 10 (stop on a breach and explain it in the report). Row 8 has four seams, so the bodies go to FOUR new modules in this one PR, as cut 9 split its row over three.

    Anchor and reader reading before the cut (ruling item 2), on 05cc3ee953, with the trial cut in place rather than blanked bodies:

    • ADR anchors: every ADR symbol anchor into security-plugin.ts was mapped with extractAnchors and symbolSegmentResolution from scripts/symbol-anchors.mjs. That is 27 anchors over 18 symbols, and none changes class at the trial head. No ADR anchors a row 8 member or the moved constant.
    • node scripts/check-adr-symbol-anchors.mjs gives byte-identical output at base and at the trial head (exit 0 at both).
    • pnpm check:adr-anchors exits 0 at both ends: 81 anchored files, and decision letters 1978 to 1969 (above). Citations go from 52223 to 52224 and the files holding one from 6651 to 6654: three of the modules cite an ADR, and the one extra citation is the depth parameter comment, which both the class signature and the function's signature carry.
    • Docs and checklist anchors by path: security-plugin.ts#X is cited 22 times over 13 symbols outside docs/adr/** (the liveness ledgers, the system-context page, the conversions registry, the platform checklist), and none changes class. The checklist's security-plugin.ts#declareEveryone (access-security.json:2032) resolves on the method, which stays. node scripts/check-platform-checklist.mjs, pnpm check:platform-checklist, check-scripts-symbol-anchors (and its --self-test) and pnpm --filter @objectstack/spec check:liveness give the same output at base and at the trial head.
    • Censuses, with the four modules tracked (git add -N), compared site by site with the module paths mapped back to security-plugin.ts:
      • isystem-census: 148 sites at both ends, 0 differences, and the same role counts (read 153, declaration 38, key 438, other 9). The one moved read is explainAccessForCaller's callerContext?.isSystem, in the explain module. Files holding a site go from 74 to 75.
      • check-system-context-census exits 0 at both ends with the same totals (148 sites, 127 symbols, 140 cited against 140 required, 163 anchors), files 74 to 75. With the page left untouched it reports 2 problems.
      • tenant-audit-census: 238 sites, 0 differences, 175 non-engine calls at both ends; no moved body holds a write verb (the explanation's and the verdict's ql.findOne and ql.find are reads). check-tenant-audit-census exits 0 at both ends.
      • pnpm check:query-options-erasure exits 0 at both ends with the same output.
    • The plugin-security: delegation probes and diagnostics (canReadObject / canWriteObject / canExport, field mask, explain) answer for an on-behalf-of context the operation prologue now refuses (PR #25101) #25107 pin at the trial head, resolutions and asks per file: explain module 1 and 1, security-plugin.ts 1 and 1 (start(); 2 and 2 at base), the five other files unchanged.
    • Other readers by path:
      • row-serving-door-exposure.pin.test.ts and operation-private-keys.pin.test.ts (@objectstack/core) pass at the trial head, 8 of 8. No moved body names an admission.
      • rest-server.ts:5953 names SecurityPlugin.explainAccessForCaller in prose; the method stays.
      • No security-plugin.ts:N line citation points into the moved ranges, and no test replaces or spies on a moved method on the plugin instance.
    • Local trial at the trial head: pnpm typecheck (with the test layer), eslint on the six files and the package build exit 0, and the plugin-security suite passes (214 files, 4465 passed, 45 skipped).
    • Bundle order, disclosed: builtin-positions.ts, manifest.ts and objects/sys-audience-binding-suggestion.object.ts are now emitted just before the four modules instead of between security-plugin.ts's chunks. None of them imports security-plugin.ts, and their top level only declares constants.

    Serial check: of the 20 open PRs, one touches security-plugin.ts and system-context.mdx: #25483 (#25251's first PR, this project's), which adds one import near :29, a call in start() near :2577 and a new page row 1b after row 1. None of those lines is in row 8 or in an import line this cut edits. A scratch merge of #25483's head onto 05cc3ee953, with this cut applied on top, applies cleanly. No other open PR touches security-plugin.ts, the four module names, system-context.mdx, delegated-organization-membership.test.ts, the census scripts, the platform checklist or the plugin-security anchor entries. I re-check before pr_create and yield on a new conflict.

    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 start() is scoped.
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: 6369034620
    Resume-from: none


    Generated by Claude Code

  19. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 25223,
    "status": "done",
    "branch": "claude/project-thread-3fzya4",
    "pr": "#25516",
    "session": "session_017BoLT8Gaa8t9a7AWVH8qZa — this run's harness-stamped id, on the Claude-Session: trailer of every commit on the branch",
    "premise_still_valid": true,
    "summary": "Cut 10 of the security-plugin.ts split, row 8 of the cut 1 plan (declareEveryone..checkAuthoredRowWrite); claim 6369634056 on the merged main after cut 9 (Ruling-ref 6353073966; d.ts bar 6348891160). The row has four seams, so the bodies of ten methods moved unedited to four new modules in this one PR. To src/security-plugin-everyone-anchor.ts (host EveryoneAnchorHost): declareEveryone (ADR-0090 D5/D7, ADR-0131 D3, #22801) and declareBuiltinPositions (synchronous, stays synchronous; ADR-0131 D2). To src/security-plugin-explain-access.ts (host ExplainAccessHost): explainAccessForCaller (public; ADR-0090 D6/D12, #22792, #22851). To src/security-plugin-sharing-verdicts.ts (host SharingVerdictsHost): resolveSharingReadFilter (#4467), resolveSharingCanEdit (#5386), resolveWriteScopeForSharing (ADR-0057 D1), resolveSharingWriteVerdict (#5492, #6428), resolvePreImageFloorDrop (#19963) and delegatorOwnWriteFloorOptions (ADR-0090 D10). To src/security-plugin-authored-row-write.ts (host AuthoredRowWriteHost): checkAuthoredRowWrite (public; #5493, #7281, #8059). Shape B as in cuts 1 to 9: each method keeps name, signature, docblock and position less async and hands its body one host literal typed as the Pick of its host over exactly the members it reads; methods as late-bound closures; baselinePermissionSets (declared readonly) by value; delegatedAdminGate, epoch, logger, metadata, permissionEvaluator, ql, resolveKernelService and tenancyPosture (none declared readonly) as getters. Two members of the row stay whole: everyoneBaseline (6 body lines against a 6-line stub) and destroy (3). Module-level: AUTHORED_ROW_WRITE_PROBE_CONTEXT, read only by checkAuthoredRowWrite, moved byte-identical with its docblock and stays unexported; absentUnderCallerRead and unreadableUnderCallerRead stay in security-plugin.ts and gain export only (the first is shared with the write path step 2.7; neither signature names another module type, so the cut 6 d.ts trap does not arise; index.ts re-exports neither); RlsFilterOptions named in the modules through the class signature (computeLayeredRlsFilter fifth parameter less undefined), so it gains no export. Import names only the moved bodies read leave security-plugin.ts (explainAccessForAsker, buildContextForUser, the whole builtin-positions import, readAcceptedEveryoneSets, readDeclaredCapabilityContext, vetOrganizationClaim, type ExplainOperation). Two path readers re-pointed in the same PR, as cut 9 did: system-context.mdx row 9 (path only, symbol and row text unchanged) and the #25107 enumeration pin (explain module added; title six to seven files; asks equal resolutions in every file at head). Disclosed: one receiver changes. resolveKernelService is a function-valued field that the explanation and three sharing verdicts call through the host, so its this is the host; the only function ever assigned to it is the arrow function start() assigns, which reads no this, and no test or other file assigns it. Disclosed: bundle order. builtin-positions.ts, manifest.ts and sys-audience-binding-suggestion.object.ts are now emitted ahead of the four modules; none imports security-plugin.ts and their top level only declares constants. No ADR anchor entry added (no paragraph of the security-plugin.ts entry describes these bodies). docs/adr untouched, no token planted. Published d.ts byte-identical to base. No open question. The card stays open for start(), which is Tier H and goes to the seat before any PR.",
    "tests": "pnpm --filter @objectstack/plugin-security test (vitest run) at head 38392cb: Test Files 214 passed (214), Tests 4465 passed | 45 skipped (4510). Base 05cc3ee: the same counts. packages/core row-serving-door-exposure.pin.test.ts and operation-private-keys.pin.test.ts 8 of 8. Move-only, so no ablation. typecheck exit 0 at head and base (tsc --noEmit, the scripts tsconfig, check:test-typecheck). eslint over the six touched source and test files exit 0. Package build exit 0; each side was a full turbo run build in its own worktree (both exit 0), and a package build of the same trees before the claim gave the same d.ts bytes. Move proof: each moved body has the same TypeScript AST token sequence as the base body and each of the 549 body lines is the base line minus two leading spaces; parameters, return types and async match. The explanation body holds five multi-line JSDoc text tokens (the docblocks of its inner closures) whose continuation lines are re-indented one level like every other body line, so the comparison dedents those JSDoc and JSDocText tokens one level and only those; a string or template token that spans lines is compared byte for byte; the explanation 1,731 tokens are then equal. Four negative controls fail the check: explainAccessForCaller callerContext?.isSystem === true changed to === false; resolveSharingCanEdit fail-closed return false of the probe catch changed to return true; checkAuthoredRowWrite non-write abstention return abstain changed to return admit; one word inside one of the explanation inner JSDoc blocks (The two contexts changed to The three contexts), which shows the JSDoc dedent hides no change of words. Class: each moved method keeps docblock and signature less async; every other member (116 named plus the constructor; 127 at base and head) byte-identical, everyoneBaseline and destroy included. Top level of security-plugin.ts: 97 of 106 base statements byte-identical; the other nine are five imports that lose the names above, the builtin-positions import (gone), AUTHORED_ROW_WRITE_PROBE_CONTEXT (now in the authored-row module, byte for byte) and the two functions that gain export; four new statements, the module imports. git diff --color-moved=plain --color-moved-ws=allow-indentation-change 05cc3ee 38392cb over the five source files: 581 of 596 deleted and 679 of 1,184 added lines moved; the 15 deleted-not-moved are the nine signatures that lose async, four import lines that change, and the two functions that gain export. 1,785 changed lines (+1,187 / -598), 7 files, under the 3,000-line manual-merge line; security-plugin.ts 8,852 to 8,406, the new modules 140, 433, 277 and 184 lines.",
    "dts_block": {
    "files": "packages/plugins/plugin-security dist/index.d.ts and dist/index.d.mts, base 05cc3ee and head 38392cb, each from a full turbo run build in its own worktree",
    "raw_sha256": "base index.d.ts 7d67966ce82e00a1531807b3b0008f16c489fc2c3199f9ad67078302eb2803da · head index.d.ts 7d67966ce82e00a1531807b3b0008f16c489fc2c3199f9ad67078302eb2803da · base index.d.mts and head index.d.mts the same hash",
    "normalised_sha256": "base b53e9025f794e20debd2245afed5ceb1a0ad48109a2a21cc19232a562ccd9cb4 · head b53e9025f794e20debd2245afed5ceb1a0ad48109a2a21cc19232a562ccd9cb4 (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": "Each a one-line edit on a copy of the head index.d.ts. Line 557, private resolveSharingReadFilter, made public: raw 8c8fad488058d08a54e23b5410b539b7eecad71a0e749b0133a61280e146bdde, normalised f02f50957a57e31fd943e00fa0ef74113a2f0efdc4af8e3ee5b963c7e932a3c6. Line 447 renamed to private declareEveryoneRenamed: raw 7af56294d477cdbc07b22567c87a110bbc2c36d37724cc47ed3652dc085a8ab4, normalised 44d50a2b10e166373f11f31ef5e046c1a05ab9f5613e9d6b762c03c7ca15ec88. Line 745, the public checkAuthoredRowWrite signature, context?: any changed to context?: unknown: raw b75e990785a61245092adf8414acba79b6fe27f330ebd7555e9a808642802556, normalised 6b7331a568193fda94c1e45eabe85f5704d61c6ef99639bb197b7f11510b81b6. All three move the normalised hash.",
    "exports": "ESM and CJS bundles loaded at runtime (require and import of the built bundles): 66 names each, base and head; sorted-list sha256 83576430f83cf60cecbcc5c0e80214f16c626a8e66f700294fc6a44361423116 on all four, as at cuts 6 to 9. Bundle bytes differ (index.js dfa83a54 to 78d8f0eb, index.mjs 18c4d919 to 5d290484): code moved within them. Every value the modules import comes from a file inside the package or from a specifier security-plugin.ts already imports, so the distinct external specifiers are the same at base and head (16 require in index.js, 19 from in index.mjs).",
    "note": "The same bytes cut 9 measured, so neither cut 9 merge nor main later commits moved this package d.ts before this base. security-plugin.ts gains two exports (absentUnderCallerRead, unreadableUnderCallerRead); each takes one read callback and answers a boolean promise, so no type of another module enters the d.ts, and index.ts re-exports neither. The two public methods carry full signatures and docblocks (explainAccessForCaller line 525, checkAuthoredRowWrite 745); the eight private ones name and docblock only (declareEveryone 447, declareBuiltinPositions 483, the six sharing verdicts 557 to 676); those lines are the same in both files."
    },
    "adr_anchor_block": {
    "gate": "node scripts/check-adr-symbol-anchors.mjs — base 05cc3ee exit 0, head 38392cb exit 0, byte-identical output; both print 2384 anchors across 142 records resolve, 466 symbol (440 declaration, 26 literal), 1890 file-level, 28 cross-repo, 6 exempt, 18 continuation. Not derived by dispatch-gates for this change set (#21697); run by hand (ruling item 2). On a scratch merge with origin/main 842a0ae (17 commits past this base): exit 0, the same 2384.",
    "map": "Read BEFORE the claim on 05cc3ee with the whole cut applied as a local trial: every ADR symbol anchor naming security-plugin.ts (27 anchors, 18 symbols), measured with extractAnchors and symbolSegmentResolution from scripts/symbol-anchors.mjs: no anchor changed class. No ADR anchors a row 8 member or the moved constant. At head all stay in security-plugin.ts with their base class. Base line to head line, class: #CBP_MAX_CHAIN_DEPTH (ADR-0055:135) :712 to :744 declaration top-level; #SecurityPlugin (ADR-0049:49, ADR-0138:248, :265) :1344 to :1346 declaration; #SecurityPlugin.getMetadataReadableFields (ADR-0138:153) :6030 to :5584 declaration; #SecurityPlugin.resolvePermissionSetsForContextUnmemoized (ADR-0138:427) :6624 to :6178 declaration; #assertControlledByParentWrite (ADR-0055:58, :125) :8109 to :7663 declaration; #assertMasterRowEditable (ADR-0055:125) :8235 to :7789 declaration; #collectRLSPolicies (ADR-0105:93) :8289 to :7843 declaration; #composeWriteTenantWall (ADR-0123:4) :7447 to :7001 declaration; #computeControlledByParentFilter (ADR-0055:5, :123, ADR-0139:246) :7755 to :7309 declaration; #computeDelegatedReadFilter (ADR-0139:223) :5949 to :5503 declaration; #computeRlsFilter (ADR-0055:24) :7155 to :6709 declaration; #getReadFilter (ADR-0096:181, :182, :183, :199, ADR-0139:222) :5895 to :5449 declaration; #hasPlatformAdminPosture (ADR-0099:317) :7182 to :6736 declaration; #isPrincipalLessContext (ADR-0138:152) :585 to :617 declaration top-level; #manage_metadata (ADR-0099:68) :338 to :370 literal; #nav_permission_sets (ADR-0126:406) :1772 to :1774 literal (init); #rlsFilter (ADR-0055:24) :4346 to :4348 declaration (local in start); #writeCheckPolicies (ADR-0066:112) :1239 to :1241 declaration top-level. Non-ADR corpora (spec liveness, platform checklist, system-context.mdx, spec conversions/registry.ts): 22 anchors over 13 symbols naming security-plugin.ts, each resolving at head with its base class, 0 changed. Every path-qualified plugin-security security-plugin*.ts anchor outside docs/adr: 32 at base and head, 0 unresolved; at head 22 in security-plugin.ts, 1 in the explain module (the re-pointed row 9), 9 in the modules of cuts 3, 5 and 9, as at base.",
    "adr_anchors": "pnpm check:adr-anchors exit 0 at base and head; anchored files 81 at both. DECISION LETTERS ON THE ANCHORED SURFACE: base 05cc3ee 1978, head 38392cb 1969. Nine citations leave with the moved bodies: explainAccessForCaller 7 (ADR-0090 D6, ADR-0090 D10, ADR-0090 D12, ADR-0105 D3 x2, ADR-0111 D3, ADR-0111 D5), resolveSharingReadFilter 1 (ADR-0057 D1), checkAuthoredRowWrite 1 (ADR-0058 D4); the other bodies, the moved constant docblock, the four module headers and the four new import comments cite none. resolveSharingReadFilter depth parameter comment also cites ADR-0090 D10; the parameter list belongs to the signature, which stays in the class, so that letter stays on the anchored surface (a body-only count gives 1968; measured 1969). Measured directly: security-plugin.ts holds 258 such citations at base and 249 at head, the 9 that leave being exactly these. Every moved citation is still in the tree in the module its body moved to; tree-wide resolving citations 52223 across 6651 files at base to 52224 across 6654 at head, the one added being the depth comment that both signatures now carry. No entry added for any module: no paragraph of the security-plugin.ts entry (ADR-0055 §1, ADR-0106 D7, ADR-0123 D2, ADR-0138 D1) describes a moved body. Scratch merge with origin/main 842a0ae: 82 anchored files and 1969 letters, against 82 and 1978 on that main alone, so the same 9 leave; resolving citations 52255 across 6663 files there to 52256 across 6666."
    },
    "gates": "Local on 38392cb, each side in its own worktree after a full turbo run build (both exit 0): dispatch-gates --commands, 88 commands; each run with its exit captured before any pipe; --ran: 88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero: all 88 recorded an exit code, none is 3). pnpm check:query-options-erasure is derived this time. Run by hand, not derived, all exit 0 at base and head: check-adr-symbol-anchors, pnpm check:adr-anchors, check-platform-checklist (and pnpm check:platform-checklist), check-scripts-symbol-anchors and its --self-test, isystem-census --json, tenant-audit-census --json, pnpm --filter @objectstack/spec check:liveness. The six artifact-roster gates whose roster sits under a touched directory, plus the package typecheck, run at base and head, all exit 0: check-published-list-mirrors, check:authz-resolver, check:console-injection, check:error-code-casing, check:filter-alias-parity, check:published-readme-exports. All 97 commands (88 derived and 9 hand-run) exit 0 at head and at base; 72 give identical output at both (path and timings masked). The other 25, each read: check:adr-anchors (letters above), check-system-context-census and the two census --json reads (below); check-issue-citations judges the change set (42 citations across 5 files, all resolve); counts that move by exactly the four modules (check-closing-keyword-parity and check:gitlink-declared 12029 to 12033, check:nul-bytes 12020 to 12024, check-comment-mask-corpus 10005 to 10009, check-plugin-teardown-shape 9397 to 9401, check-position-name-fold-loaders 12370 to 12374 with the same 8 references, check-undeclared-dep-imports 3316 to 3320 files and 2865 to 2872 specifiers (the seven the modules import, all declared dependencies), spec check:duration-unit-keys 3321 to 3325, check:closed-ceremony-text 4573 to 4577, check:doc-authoring 1434 to 1438 parsed sources and 104377 to 104528 strings (the new headers and stubs), check:engine-double-contract 3625 to 3629, check:error-status-conformance 3413 to 3417, check:logger-receiver-detach 3633 to 3637, check:org-identifier 3658 to 3662, check:page-declaration-shape 3666 to 3670, check:refd-timer-probe 9998 to 10002, check:type-check-coverage and -debt 3542 to 3546, check:vendor-version-stamps 10397 to 10401); check:skill-identifier-liveness 139798 to 139805 implementation word tokens, the seven words new to the tree (the four host names, the stub getter locals readEpoch and readResolveKernelService, and abstentions in the authored-row header), no citation changes. isystem-census --json: 148 sites at both, 0 differences with line numbers dropped and the modules mapped back; 1 head site in the modules (explainAccessForCaller callerContext?.isSystem, the re-pointed row 9); role counts equal (read 153, declaration 38, key 438, other 9), 5 non-elevation reads, 22 packages; files 74 to 75, scanned files 397 to 399. check-system-context-census and --self-test exit 0 at both, the same totals (148 sites, 127 symbols, 140 cited against 140 required, 163 anchors), files 74 to 75 (with the page untouched a trial reported 2 problems). tenant-audit-census --json: 238 write sites at both, 0 differences, none in the modules, the same 175 non-engine calls; scannedSources 730 to 734; check-tenant-audit-census exit 0 at both with the same output, tenant-audit-census.mdx not touched. No security-plugin.ts:N line citation in the repo points into the moved ranges (base :760 to :788 and :4912 to :5838). Docs Drift Check (bot comment 6370969173, computed on the merge with 5188e7f): advisory. 11 hand-written pages and 4 release-owned pages are reached, every one through a name the move relocated, not a changed behaviour: the manage_users string literal inside explainAccessForCaller (9 hand-written pages and releases v13, v15, v16), computeLayeredRlsFilter as a host field and a string literal (permissions-matrix.mdx, sharing-rules.mdx), checkControlledByParentWrite and explainAccessForCaller (system-context.mdx), and the /api/v1/meta/position path literal in declareBuiltinPositions (releases v18/18-0.mdx). Each moved body is unedited and each member keeps name, signature, docblock and behaviour, so no page goes stale; the one page that reads the moved lines by path (system-context.mdx) is re-pointed here; the release-owned pages are read-only and none is wrong. Scratch merge of this head with origin/main 842a0ae, 17 commits past this base: three touch this package (#25483, #25489, #25512), and in security-plugin.ts only #25483 (an import and one call at base :2601) and #25512 (an import and one call at base :4759), both outside the moved ranges. git merges clean, the same 7 files and 1,785 changed lines. There, check-adr-symbol-anchors, check:adr-anchors, check-platform-checklist, check-system-context-census (149 sites across 76 files, against 75 on that main alone: the explain module), check-tenant-audit-census (238 sites) and isystem-census --json all exit 0; turbo run build of the package and its dependencies, the package typecheck and the package suite exit 0 (Test Files 218 passed (218), Tests 4573 passed | 45 skipped (4618), the suites main added included); the three core pins row-serving-door-exposure, operation-private-keys and second-object-read-exposure 12 of 12. The base worktree was restored to 05cc3ee after. CI on head 38392cb, all completed, none failed: Auto Label success (a second run of the same name skipped by its filter), Build Core success, Build Docs success, Check Changeset success (a second run of the same name skipped by its filter), Check Documentation Links success, Check PR Size success (a second run of the same name skipped by its filter), Dogfood Regression Gate success, filter success, Flag docs affected by code changes success, Governed Surface Queue Guard success, Lint · CI, release & engine gates success, Lint · docs & code guards success, Lint · ESLint success, Lint · PM dispatch-gates success, Lint · repo & tooling gates success, Lint · scoped · query-options erasure success, Lint · scoped · slot lookup & corpus success, Lint · scoped · tooling self-tests success, Lint & Repo Gates 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, Spec property liveness success, Test 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, The card this PR closes must claim this branch success, Type Check · consumer gates success, Type Check · debt ledger success, Type Check · source gates success, Type Check · workspace success, TypeScript Type Check success; skipped by their own filters: 18.x upgrade harness (opt-in), Console Pin Gate, the sharded Dogfood Regression Gate matrix, Dogfood Verify CLI, Packed-tarball smoke (opt-in), Temporal Conformance (live PG + MySQL). PR mergeable_state clean.",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "none in this cut — every GitHub read was gh api GET (card comments, open PR files for the serial check, PR state, check runs, the bot comment). Also claude-code-remote subscribe_pr_activity on #25516 (a session subscription, not a GitHub write). No MCP write tool.",
    "api_writes": "5 strokes, all through the scripts/pm relay or git: (1) post-stamped claim comment 6369634056 on #25223 (relay run 39986787193); (2) git push of claude/project-thread-3fzya4 to 38392cb (new branch from 05cc3ee; the merged branch had been deleted by the queue); (3) fleet-write pr_create, PR #25516 draft with assignee os-sales and label skip-changeset (relay run 39996328135; body read back identical, 42073 bytes, ending with both footers); (4) fleet-write pr_request_reviewers os-sales (relay run 39996468770); (5) this report, via post-stamped.",
    "open_questions": [],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  20. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed cut 10 (plan row 8) · domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · 2026-10-23T19:10Z

    reading value
    PR #25516 MERGED through the queue (ACCEPT 6372128178), head 38392cb1b7
    origin/main carries 82f5a111b4

    State: the card stays open and dispatched (Part of). start() is the last row. It is Tier H, so the card thread brings it to the seat before any claim or PR.


    Generated by Claude Code

  21. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p2 · series card, cut 11 of the security-plugin.ts split: the start() row, part 1 — the security service registration and the seed-ownership claim move out · 2026-10-23T19:49Z
    Ruling-ref: 6353073966 (item 2 for the anchor reading below); the series' .d.ts bar stays ruling 6348891160 item 1
    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, restarted from origin/main at 82f5a111b4, which contains cut 10's merge. It names no issue, so this claim is the identity record.
    Worktree: objectstack-issue-25223
    Domain: domain:services (the seat post's name; #25247 aliases it to domain:server)
    Seat: domain:services#1 (seat post #6021)

    Why now: cut 10's landing note (6372724998) left start() as the last row and asked the card thread to bring it to the seat before any claim or PR. It did. The plan mapped start() (:1907-:4934 at 82f5a111b4, 3,028 lines) into four regions: R1 setup, R2 the security service registration, R3 the authz middleware and R4 the post-middleware hooks. The seat agreed this order: this cut (R2 plus the seed claim) lands first through the normal path, then cut 12 (R3) is claimed afresh on the merged main. Cut 12 is Tier H on two counts, the ADR-0055:24 re-anchor and its size, and its own claim will say so. R1 stays: it writes 11 members. R4 is cut 13 or the final report. init() stays: ADR-0126:406 #nav_permission_sets resolves only on its literal.

    File surface, cut 11 (stop on a breach and explain it in the report). Two seams, so two new modules in this one PR.

    • packages/plugins/plugin-security/src/security-plugin.ts, at 82f5a111b4 (8442 lines):
      • To security-plugin-security-service.ts (host SecurityServiceHost), ONE STATEMENT of start():
        • the try statement at :2266-:2560 (295 lines). It builds the ISecurityService literal that every door bypassing the engine middleware asks, and registers it as the security kernel service. The comment above it (:2259-:2265, ADR-0021 D-C) stays in start(), above its call.
        • It becomes export function registerSecurityService(this: Pick<SecurityServiceHost, …>, ctx: PluginContext, ql: IObjectQLEngine, metadata: IMetadataService): void. Its body is that one statement, re-indented one level and otherwise unedited.
        • Why the parameters: the statement closes over three of start()'s locals and nothing else. It has no arguments, super, new.target or bare this, and no return, await, break or continue at its top level (measured). So the three locals become parameters, typed as the statement saw them there, where both handles are already known to be present.
        • The stub in start() is one block at the same position, holding the getter reads, one host literal and registerSecurityService.call(host, ctx, ql, metadata);. Because it is a block, its consts do not reach the rest of start().
        • The host 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 cuts 1 to 10 applied it to method bodies:
          • 15 methods as late-bound closures (canExport, canReadObject, checkAuthoredRowWrite, checkControlledByParentWrite, declareEveryone, describePositionGrants, explainAccessForCaller, getEffectiveObjectPermissions, getMetadataReadableFields, getObjectSecurityMeta, getQueryableFields, getReadFilter, getReadableFields, getWritableFields, resolvePermissionSetsForContext);
          • acceptedDefaultPermissionSets and ownershipFloorAlternates, declared readonly, by value;
          • delegatedAdminGate, logger, permissionEvaluator, ql and tenancyPosture, which are not, as getters.
        • It writes no member. No receiver changes: every service member that binds a plugin method was already an arrow calling this.m(…), and this.m is now the host's closure, which calls the plugin's method on the plugin at call time. It calls no function-valued field and hands out no unbound method reference.
      • To security-plugin-seed-claim.ts (host SeedClaimHost):
        • the body of the private method claimSeedOwnershipOnSettle (:1873-:1905, 28 body lines), shape B. The method keeps its name, signature, docblock and position, less async, and init()'s app:seeded subscription still calls it on the plugin. bootstrapPermissionSets is readonly and goes by value. claimTargetAdminUserId is not, because R4 assigns it at :4596, so it goes as a getter.
        • the two module-level readers it asks the published seed-settlement contract through: readSeedSettlementSnapshot (:1254-:1273) and readSeededRecordIds (:1275-:1293). They move with their docblocks, unedited but for export, and security-plugin.ts imports them back, because R4's promotion-time pass reads both (:4550, :4590).
        • Why they move instead of gaining export in place: each signature names a contract type, and cut 6 measured that an export from security-plugin.ts whose signature names another module's type reorders the .d.ts. The .d.ts was measured anyway (below).
      • Import names that only the moved code reads leave security-plugin.ts (16 in total), and the modules import them from the same specifiers:
        • From R2: resolveDelegatorContext, delegatedOrganizationRefusal and d10NarrowingStatement (./explain-engine.js); listAudienceBindingSuggestions, confirmAudienceBindingSuggestion, dismissAudienceBindingSuggestion and the types SuggestionDeps and SuggestionListFilter (./suggested-audience-bindings.js); the type OwnershipFloorAlternate (./platform-ownership-policies.js); the type ObjectAccessScope (@objectstack/spec/security); and the types ISecurityService and DelegationNarrowing (@objectstack/spec/contracts).
        • From the seed claim: SEED_SETTLEMENT_SERVICE and the type ISeedSettlementService (@objectstack/spec/contracts), findExistingPlatformAdmin (./bootstrap-platform-admin.js) and the whole ./claim-seed-ownership.js import (claimSeedOwnership). That module has no top-level side effect.
        • Disclosed: one staying docblock, computeDelegatedReadFilter's, names resolveDelegatorContext in a {@link}. The name is no longer imported, so that link is now plain text, like the bare-member {@link computeRlsFilter} beside it. No docblock is edited. Every other staying mention of a dropped name is in backticks or prose.
    • The three readers the seat named, re-pointed in this PR. Each fails without its edit (measured with the edit reverted):
      • content/docs/permissions/system-context.mdx, row 7. R2 holds 3 of start()'s 4 isSystem reads (:2317, :2339, :2368), and check-system-context-census keys each read by file and enclosing symbol. Row 7's anchor changes from security-plugin.ts#start to security-plugin-security-service.ts#registerSecurityService. The symbol name changes because the read now sits in the function. The row text and every other anchor stay, and rows 1, 2 and 30 keep #start (R3's read). With the page untouched, the census reports 1 problem ([file-without-a-row] for the new module). With the re-point it exits 0.
      • packages/plugins/plugin-security/src/delegated-organization-membership.test.ts, the plugin-security: delegation probes and diagnostics (canReadObject / canWriteObject / canExport, field mask, explain) answer for an on-behalf-of context the operation prologue now refuses (PR #25101) #25107 enumeration pin. R2 holds the file's only delegator resolution and organization ask. So in the expected list, security-plugin-security-service.ts replaces security-plugin.ts, and the count stays at seven files. The rule it pins, asks equal resolutions in every file, is unchanged, and it holds: the new module has 1 and 1, and security-plugin.ts has 0 and 0. Unedited, it fails on the list.
      • packages/core/src/security/row-serving-door-exposure.pin.test.ts. Every file that calls canReadObject must be classified. The service literal's binding this.canReadObject(…) now sits in the new module, which gains a not-a-door entry: "the provider's service registration: it binds the admission into the security service the plugin registers; it serves no rows". security-plugin.ts stays a caller, through the stub's closure and describePositionGrants' own ask, so its reason no longer says it binds the admission into the service. It now says "the provider: it implements the admission, hands it to its service registration, and asks it itself only to decide whether describePositionGrants may count a position's holders; it serves no rows". Unedited, it fails with the new module unclassified.
    • scripts/adr-anchors/: ONE new entry, packages__plugins__plugin-security__src__security-plugin-seed-claim.ts.json (adrs: ["ADR-0138"]).
      • Its invariant is the ADR-0138 D1 paragraph of the security-plugin.ts entry, verbatim. That paragraph names claimSeedOwnershipOnSettle and readSeededRecordIds, whose bodies now sit in the module. Cut 9 carried ADR-0106 D7 into security-plugin-field-readers.ts the same way.
      • The security-plugin.ts entry is unchanged and stays true: the method, the promotion-time pass and the import of readSeededRecordIds stay in that file.
      • The security-service module gets no entry. No paragraph describes R2: ADR-0055 §1 is the relation reader, ADR-0106 D7 is the readable-field asymmetry (in the field-readers module since cut 9), and ADR-0123 D2 is step 3.7's refusal (R3).
    • Decision letters. check:adr-anchors counts them only on the anchored surface, so the count goes from 1970 at base to 1958 at the trial head. Anchored files go from 82 to 83. The −12 is:
      • −13: R2's letters leave the anchored surface (ADR-0090 D5 ×3, D6, D10 and D12; ADR-0105 D3 and D8; ADR-0106 D7 ×2; ADR-0111 D1 and D2; ADR-0131 D3);
      • 0: readSeededRecordIds' docblock carries ADR-0138 D1, and it stays on the anchored surface, now in an anchored module;
      • +1: the new entry's invariant text.
      • Measured per file: security-plugin.ts holds 250 such citations at base and 236 at the trial head. The security-service module holds 13, and the seed-claim module 1. No letter is planted and none is invented.
    • docs/adr/**: not touched. No governed path is touched, so this is a normal landing.
    • Every other 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. None other was found (below).
    • .d.ts: ruling 6348891160 item 1, with the same four-hash block, normaliser, raw line count and positive control as cuts 1 to 10. At the trial head, dist/index.d.ts and index.d.mts are byte-identical to base (5e7e8df6…), so the cut-6 trap does not arise.
    • No changeset (skip-changeset).
    • ⛔ Not touched: R1, R3, R4, init(), every other section of the class, and any behaviour.

    Anchor and reader reading before the cut (ruling item 2), on 82f5a111b4, with the trial cut in place:

    • ADR anchors: every ADR symbol anchor into security-plugin.ts was mapped with extractAnchors and symbolSegmentResolution from scripts/symbol-anchors.mjs: 18 symbols, none changing class at the trial head.
      • No ADR anchors claimSeedOwnershipOnSettle, registerSecurityService or the two readers.
      • Two anchored members are named inside R2's service literal: #getReadFilter (ADR-0096:181-183, :199, ADR-0139:222) and #SecurityPlugin.getMetadataReadableFields (ADR-0138:153). Their names are also inside the stub's host literal and its Pick<…>. Both still resolve as declaration at both ends, on the methods.
    • node scripts/check-adr-symbol-anchors.mjs gives the same output at base and at the trial head (exit 0 at both).
    • pnpm check:adr-anchors exits 0 at both ends: anchored files 82 to 83, letters 1970 to 1958 (above). Citations go from 52262 to 52264 (the new entry's adrs list and its invariant), and the files holding one from 6671 to 6674 (the two modules and the entry).
    • Docs and checklist anchors by path: across the ADR, liveness, page, conversions and checklist corpora, security-plugin.ts#X is cited 48 times over 26 symbols at base and 47 at the trial head, and none changes class. The one ref that leaves is row 7's (above).
      • node scripts/check-platform-checklist.mjs, pnpm check:platform-checklist, check-scripts-symbol-anchors (and its --self-test) and pnpm --filter @objectstack/spec check:liveness run at both ends (results in the report).
    • Censuses, with the two modules tracked (git add -N), compared site by site with the module paths mapped back to security-plugin.ts and registerSecurityService mapped back to start:
      • isystem-census: 149 sites at both ends, 0 differences, and the same role counts (read 154, declaration 39, key 440, other 9). Files holding a site go from 76 to 77. The three moved reads are hasWriteBypass's, resolveWriteScope's and describeDelegationNarrowing's system short-circuits, now in the security-service module.
      • check-system-context-census exits 0 at both ends: 149 sites; symbols 128 to 129; cited 141 to 142 against as many required; files 76 to 77; 164 anchors.
      • tenant-audit-census: 238 sites, 0 differences, 176 non-engine calls at both ends. No moved body holds a write verb. The seed claim writes through claimSeedOwnership, which stays in its own file.
      • pnpm check:query-options-erasure runs at both ends.
    • Bundle order, disclosed. The seed-claim module imports bootstrap-platform-admin.ts and claim-seed-ownership.ts. So those two and platform-admin-standing-audit.ts are now emitted after the two new modules and ahead of ownership-floor-alternates.ts and security-plugin.ts's first chunk, instead of after predicate-guard.ts. Their top level only declares (one constant is computed from an imported constant).

    Serial check: of the 9 open PRs, one touches security-plugin.ts: #25528 (#25234's second PR, another project's). It adds one import after the rls-filter import (:173 here), a loop in R1 after the resolver's registration log (:2167 here), and edits stageRlsMembership. None of those lines is in R2, in the seed claim, in the readers or in an import line this cut edits. A scratch merge of #25528's head with this cut applies cleanly (git merge-tree, exit 0). No open PR touches the two module names, system-context.mdx, the two pins, the census scripts or the plugin-security anchor entries. I re-check before pr_create and yield on a new conflict.

    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 12 is claimed.
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: 6372724998
    Resume-from: none


    Generated by Claude Code

  22. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 25223,
    "status": "done",
    "branch": "claude/project-thread-3fzya4",
    "pr": "#25530",
    "session": "session_017BoLT8Gaa8t9a7AWVH8qZa — this run's harness-stamped id, on the Claude-Session: trailer of every commit on the branch",
    "premise_still_valid": true,
    "summary": "Cut 11 of the security-plugin.ts split, the first part of the start() row; claim 6373426297 on main 82f5a11 after cut 10 (Ruling-ref 6353073966; d.ts bar 6348891160). Before any claim the card thread mapped start() (:1907-:4934, 3,028 lines) into R1 setup, R2 the security service registration, R3 the authz middleware and R4 the post-middleware hooks, and took the plan to the seat, as cut 10's landing note asked; the seat agreed: this cut (R2 plus the seed claim) first through the normal path, then cut 12 (R3, Tier H) on a fresh claim on the merged main, then R4 as cut 13 or the final report. Moved, unedited: (1) ONE STATEMENT of start(), the try at :2266-:2560 that builds the ISecurityService literal and registers the security kernel service, to src/security-plugin-security-service.ts as registerSecurityService(this: Pick of SecurityServiceHost, ctx: PluginContext, ql: IObjectQLEngine, metadata: IMetadataService): void. It closes over start()'s ctx, ql and metadata and nothing else (no arguments, super, new.target, bare this, or top-level return/await/break/continue; measured on the AST), so they are parameters; start() keeps the statement's position and its comment (:2259-:2265) and calls it from one block holding the getter reads, one host literal and the .call. Host: 15 methods as late-bound closures, acceptedDefaultPermissionSets and ownershipFloorAlternates (readonly) by value, delegatedAdminGate, logger, permissionEvaluator, ql and tenancyPosture as getters; no member writes; no receiver changes. (2) The body of claimSeedOwnershipOnSettle (:1873-:1905) to src/security-plugin-seed-claim.ts (host SeedClaimHost: bootstrapPermissionSets by value, claimTargetAdminUserId as a getter because R4 assigns it), shape B, the method keeping name, signature, docblock and position less async; with it the two seed-settlement readers readSeedSettlementSnapshot (:1254-:1273) and readSeededRecordIds (:1275-:1293), moved with their docblocks and gaining export only, imported back by security-plugin.ts for R4 (:4550, :4590) — moved rather than exported in place because each signature names a contract type (the cut 6 trap). 16 import names only the moved code reads leave security-plugin.ts. Disclosed: computeDelegatedReadFilter's staying docblock has a {@link resolveDelegatorContext} that is now plain text (no docblock edited). Three readers re-pointed in the same PR, each failing with its edit reverted: system-context.mdx row 7 (#start to security-plugin-security-service.ts#registerSecurityService; untouched page = 1 problem, file-without-a-row); the #25107 enumeration pin (the new module replaces security-plugin.ts, seven files, 1/1 and 0/0); the core row-serving-door-exposure pin (new not-a-door entry for the module, and security-plugin.ts's reason reworded truthfully). One new anchor entry, security-plugin-seed-claim.ts, carrying the security-plugin.ts entry's ADR-0138 D1 paragraph verbatim (cut 9 precedent); the security-plugin.ts entry is unchanged and stays true. No docs/adr/** and no governed path: a normal landing. 1,010 changed lines (+629 / -381), 7 files; security-plugin.ts 8,442 to 8,120; the modules 418 and 139 lines.",
    "tests": "pnpm --filter @objectstack/plugin-security test (vitest run) at head 5a4747b: Test Files 218 passed (218), Tests 4573 passed | 45 skipped (4618). Base 82f5a11: the same counts. packages/core row-serving-door-exposure.pin.test.ts and operation-private-keys.pin.test.ts 8 of 8 at both ends; with each reader edit reverted the core pin fails (the module unclassified) and so does the #25107 pin (its list). Move-only, so no ablation. typecheck exit 0 at head and base; eslint over the five touched source and test files exit 0; package build exit 0. Move proof: registerSecurityService's body is one statement with the base try's TypeScript token sequence (1,296 tokens), each of its 295 lines the base line less two spaces (multi-line JSDoc text tokens dedented one level, only those); start()'s head text equals base outside that statement, now a 33-line stub block; claimSeedOwnershipOnSettle's body has the base token sequence (136 tokens, 28 lines less two spaces), parameters, return type and async matching; the two readers' module text is the base text with export added. Class: every other member (124 named plus the constructor; 127 at both) byte-identical. Top level: 101 of 110 base statements byte-identical; the other nine are six imports that lose names, the claim-seed-ownership import (gone) and the two readers (gone); the two new statements are the module imports; the check asserts the exact 16 dropped and 6 added import names and that no dropped name is still referenced. Seven negative controls fail the check: a token in the registration (logger.info to logger.debug); one line indented one more space with tokens unchanged; an edit in R1 ahead of the stub; an edit in the middleware after it; return undefined to return null in readSeedSettlementSnapshot; logger.warn to logger.error in the claim; a trailing space in a top-level docblock. git diff --color-moved=plain --color-moved-ws=allow-indentation-change over the three source files: 370 of 378 deleted and 391 of 613 added lines moved (311 in the security-service module, 79 in the seed-claim module, 1 stub brace); the 8 deleted-not-moved are four type-import lines and the bootstrap-platform-admin import line the modules group differently, the two reader signatures that gain export, and the method signature that loses async.",
    "dts_block": {
    "files": "packages/plugins/plugin-security dist/index.d.ts and dist/index.d.mts, base 82f5a11 and head 5a4747b, each from a full turbo run build in its own worktree",
    "raw_sha256": "base index.d.ts 5e7e8df69375e6606767eee938111aaaf0b9bac4dd9c728689c343be9e5f7702 · head index.d.ts 5e7e8df69375e6606767eee938111aaaf0b9bac4dd9c728689c343be9e5f7702 · base index.d.mts and head index.d.mts the same hash",
    "normalised_sha256": "base 1e2e8299c2b3aa8502ae3684a5a8a4f7148b2211876a0a581f20d27677531ed9 · head 1e2e8299c2b3aa8502ae3684a5a8a4f7148b2211876a0a581f20d27677531ed9 (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": "A real package build, not an edit of the emitted file: with private removed from claimSeedOwnershipOnSettle in the head source, pnpm --filter @objectstack/plugin-security build emits line 431 as the full public signature instead of private claimSeedOwnershipOnSettle; raw 12beec21603e70025c7a1b70d2c49fd0dd110d66a61b57e82098a6eb903fe08c, normalised 0509461ad52776d160e6875c3a99cee28dda954089413b05db158aabda6d52ff, both different from the real hashes. Source restored, a rebuild gives 5e7e8df6 again for both files.",
    "exports": "ESM and CJS bundles loaded at runtime (require and import of the built bundles): 66 names each, base and head; sorted-list sha256 83576430f83cf60cecbcc5c0e80214f16c626a8e66f700294fc6a44361423116 on all four, as at cuts 6 to 10. Bundle bytes differ (index.js a0d05e78 to b734b9f0, index.mjs 1080b070 to 51498bc7): code moved within them. Distinct external specifiers the same at base and head (17 require in index.js, 24 from in index.mjs, counted the same way at both ends).",
    "note": "14,696 lines at both. security-plugin.ts gains no export; the two readers gained export in the new module, which nothing re-exports. claimSeedOwnershipOnSettle is private, so the d.ts carries its name and docblock only (line 431). Bundle order: bootstrap-platform-admin.ts, claim-seed-ownership.ts and platform-admin-standing-audit.ts are now emitted after the two modules and ahead of ownership-floor-alternates.ts and security-plugin.ts's first chunk, instead of after predicate-guard.ts; none imports security-plugin.ts and their top level only declares."
    },
    "adr_anchor_block": {
    "gate": "node scripts/check-adr-symbol-anchors.mjs — base 82f5a11 exit 0, head 5a4747b exit 0, byte-identical output; both print 2384 anchors across 142 records resolve, 466 symbol (440 declaration, 26 literal), 1890 file-level, 28 cross-repo, 6 exempt, 18 continuation. Not derived by dispatch-gates for this change set; run by hand (ruling item 2).",
    "map": "Read BEFORE the claim on 82f5a11 with the whole cut applied as a local trial: every ADR symbol anchor naming security-plugin.ts (18 symbols), measured with extractAnchors and symbolSegmentResolution from scripts/symbol-anchors.mjs: no anchor changed class, all stay in security-plugin.ts. No ADR anchors the moved method, the two readers or the new function. #getReadFilter (ADR-0096:181-183, :199, ADR-0139:222) and #SecurityPlugin.getMetadataReadableFields (ADR-0138:153) are named inside the service literal and now also in the stub host literal and its Pick; both still resolve as declaration on the methods (:5485 to :5163, :5620 to :5298). The rest, base line to head line: #CBP_MAX_CHAIN_DEPTH :746 to :748; #SecurityPlugin :1348 to :1309; #SecurityPlugin.resolvePermissionSetsForContextUnmemoized :6214 to :5892; #assertControlledByParentWrite :7699 to :7377; #assertMasterRowEditable :7825 to :7503; #collectRLSPolicies :7879 to :7557; #composeWriteTenantWall :7037 to :6715; #computeControlledByParentFilter :7345 to :7023; #computeDelegatedReadFilter :5539 to :5217; #computeRlsFilter :6745 to :6423; #hasPlatformAdminPosture :6772 to :6450; #isPrincipalLessContext :619 to :621; #manage_metadata literal :372 to :374; #nav_permission_sets literal (init) :1776 to :1737; #rlsFilter (local in start, R3) :4360 to :4038; #writeCheckPolicies :1243 to :1245. Across the ADR, liveness, checklist, system-context page and conversions corpora: security-plugin.ts#X cited 48 times over 26 symbols at base, 47 at head, 0 changed class; the one that leaves is row 7.",
    "adr_anchors": "pnpm check:adr-anchors exit 0 at base and head; anchored files 82 to 83 (the new entry). DECISION LETTERS ON THE ANCHORED SURFACE: base 82f5a11 1970, head 5a4747b 1958. The -12: -13 the registration's letters leave with it (ADR-0090 D5 x3, D6, D10, D12; ADR-0105 D3, D8; ADR-0106 D7 x2; ADR-0111 D1, D2; ADR-0131 D3); 0 for readSeededRecordIds' docblock ADR-0138 D1, which stays anchored in the anchored module; +1 the new entry invariant. Measured per file: security-plugin.ts 250 at base, 236 at head; security-service module 13; seed-claim module 1. Tree-wide resolving citations 52262 across 6671 files to 52264 across 6674 (the entry's adrs list and invariant; the two modules and the entry). The security-service module gets no entry: no paragraph of the security-plugin.ts entry (ADR-0055 §1, ADR-0106 D7, ADR-0123 D2) describes the registration. docs/adr/** not touched; no anchor token planted."
    },
    "gates": "Local, each side in its own worktree after a full turbo run build (both exit 0), base 82f5a11 and head 5a4747b: dispatch-gates --commands, 101 commands; each run with its exit captured before any pipe; --ran: 101 derived, 101 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero: all 101 recorded an exit code, none is 3). pnpm check:adr-anchors, check-scripts-symbol-anchors and its --self-test, and pnpm check:query-options-erasure are derived this time. Run by hand, not derived, all exit 0 at base and head: check-adr-symbol-anchors, check-platform-checklist (and pnpm check:platform-checklist), isystem-census --json, tenant-audit-census --json, pnpm --filter @objectstack/spec check:liveness. Ten artifact-roster families keep their roster under a touched directory; check:engine-double-contract and check:i18n-stale-fill are derived anyway, and the other eight were run at base and head, all exit 0: check-published-list-mirrors and its --self-test, check:authz-resolver, check:console-injection, check:error-code-casing, check:filter-alias-parity, check:published-readme-exports, check-dts-references --self-test; plus the package typecheck and both test suites. All 118 commands exit 0 at head and at base; 88 give identical output at both (path and timings masked). The other 30, each read: check:adr-anchors (letters above), check-system-context-census (149 sites; files 76 to 77, symbols 128 to 129, cited 141 to 142 of as many required, 164 anchors) and the two census --json reads (isystem 149 sites, 0 differences once the modules map to security-plugin.ts and registerSecurityService to start; roles read 154, declaration 39, key 440, other 9 at both; tenant 238 write sites, 0 differences, 176 non-engine calls, scannedSources 739 to 741); check-issue-citations judges the change set (22 citations across 3 files, all resolve); the plugin-security suite's console output (parallel worker logs, ids, timestamps, stack frames carrying the file's new line numbers) with the same counts; counts that move by the two modules (and the anchor entry for whole-index readers): check-closing-keyword-parity and check:gitlink-declared 12065 to 12068, check:nul-bytes 12056 to 12059, check-comment-mask-corpus 10030 to 10032, check-plugin-teardown-shape 9421 to 9423, check-position-name-fold-loaders 12399 to 12402 (same 8 references), check-undeclared-dep-imports 3326 to 3328 files and 2885 to 2890 specifiers (all declared), check:duration-unit-keys 3331 to 3333, check:closed-ceremony-text 4594 to 4596, check:doc-authoring 1445 to 1447 sources and 105072 to 105157 strings, check:engine-double-contract 3636 to 3638, check:error-status-conformance 3424 to 3426 (same 510 producer sites), check:error-code-casing 9404 to 9406, check:logger-receiver-detach 3644 to 3646, check:org-identifier 3669 to 3671, check:page-declaration-shape 3677 to 3679, check:refd-timer-probe 10023 to 10025, check:type-check-coverage and check:type-check-debt 3553 to 3555, check:vendor-version-stamps 10422 to 10424, check:agent-test-spelling 613 to 614 files, check:pnpm-flag-spellings 454 to 455 under scripts/; check:skill-identifier-liveness 140073 to 140077 word tokens (SecurityServiceHost, SeedClaimHost, registerSecurityService, readClaimTargetAdminUserId; no citation changes); check:merge-driver only its temp directory names. CI on 5a4747b: green, 40 checks passed and 14 skipped by path filter (the body edit re-ran the PR-text checks); mergeable_state clean. Since the local runs origin/main gained 7 commits, up to 74496c6 (#25515, #25524 and #25520 among them); none touches a file this cut touches. A scratch merge of this head onto 74496c6 is clean (git merge-tree exit 0), and on it check-adr-symbol-anchors (2384), check:adr-anchors, check-system-context-census (149 sites, 77 files, 142 of 142 cited), check-tenant-audit-census and check-platform-checklist exit 0, and the #25107 pin (66 tests) and the two core pins (8) pass. There check:adr-anchors counts 84 anchored files and 1959 decision letters, against 83 and 1971 on 74496c6 alone: the same 12 leave. Docs Drift Check (advisory bot comment 6373542564) lists 13 hand-written and 6 release-owned pages naming identifiers in the moved text; move-only, so none describes changed behaviour, no page edited, no reply. Serial: #25528 (another project's, #25234) touches security-plugin.ts at an import after the rls-filter import, a loop in R1 and stageRlsMembership; none is a moved line or an import line this cut edits; git merge-tree of its head with this cut exits 0.",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "none in this cut — every GitHub read was gh api GET (card comments, open PR files and #25528's patch for the serial check, PR state, check runs, the bot comment). Also claude-code-remote subscribe_pr_activity on #25530 (a session subscription, not a GitHub write). No MCP write tool.",
    "api_writes": "6 strokes, all through the scripts/pm relay or git: (1) post-stamped claim comment 6373426297 on #25223 (relay run 40014473173); (2) git push of claude/project-thread-3fzya4 to 5a4747b (new branch from 82f5a11; the merged branch had been deleted by the queue); (3) fleet-write pr_create, PR #25530 draft with assignee os-sales and label skip-changeset (relay run 40015180413; body read back identical); (4) fleet-write pr_request_reviewers os-sales (relay run 40015306133); (5) fleet-write issue_patch of #25530's body with the Gates section (relay run 40022014854; 40279 bytes read back identical); (6) this report, via post-stamped.",
    "open_questions": [],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  23. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed cut 11 (start() security service + seed claim) · domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · 2026-10-23T21:33Z

    reading value
    PR #25530 MERGED through the queue (ACCEPT 6374612035), head 5a4747b9c7
    origin/main carries c15fd9a72b

    State: the card stays open and dispatched (Part of). Cut 12 (R3, the authz middleware) is Tier H. The seat re-measures and ACCEPTs it but does not ready or arm it, and a person merges it. The ADR-0055 #rlsFilter re-anchor goes in its own commit.


    Generated by Claude Code

  24. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p2 · series card, cut 12 of the security-plugin.ts split: the start() row, part 2 — the authorization middleware moves out · 2026-10-23T22:11Z
    ⛔ Tier H, on both counts, so this PR is a human merge. (1) Size: 4,328 changed lines (+2,260 / −2,068), over the 3,000-line line. (2) Path: it edits docs/adr/0055-master-detail-controlled-by-parent.md, one path in one anchor (ruling 6353073966 item 5, the start()-local re-anchor), in its own commit. No seat readies, queues or arms it. The seat re-measures and posts its ACCEPT; the landing then goes to the human-merge route.
    Ruling-ref: 6353073966 (item 2 for the anchor reading, item 5 for the ADR-0055 re-anchor); the series' .d.ts bar stays ruling 6348891160 item 1
    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, restarted from origin/main at 0fab7baf51, which contains cut 11's merge (c15fd9a72b). It names no issue, so this claim is the identity record.
    Worktree: objectstack-issue-25223
    Domain: domain:services (the seat post's name; #25247 aliases it to domain:server)
    Seat: domain:services#1 (seat post #6021)

    Why now: the seat's plan answer agreed the order R2 (cut 11), then R3 (this cut) claimed afresh on the merged main, Tier H on both counts, with the ADR-0055 #rlsFilter re-anchor in its own commit and the census collapsesOntoRead reading at both ends. Cut 11 merged at 21:32Z as c15fd9a72b, and the seat cleared cut 12 on current origin/main. R1 stays (it writes 11 members). R4 is cut 13 or the final report; the seat left that choice to this thread.

    File surface, cut 12 (stop on a breach and explain it in the report). One seam, one new module.

    • packages/plugins/plugin-security/src/security-plugin.ts, at 0fab7baf51 (8,120 lines; start() is :1847-:4612):
      • To security-plugin-authz-middleware.ts (host AuthzMiddlewareHost), ONE STATEMENT of start(): the ql.registerMiddleware(async (opCtx, chainNext) => { … }) call at :2241-:4181 (1,941 lines), the handler every engine operation passes through. The comment above it (// Register security middleware, :2240) and the log line after it (:4183) stay in start(). start() registers three other middlewares (:4193, :4512, :4535); they stay.
        • It becomes export function registerAuthzMiddleware(this: Pick<AuthzMiddlewareHost, …>, ctx: PluginContext, ql: IObjectQLEngine, metadata: IMetadataService): void. Its body is that one statement, re-indented one level and otherwise unedited.
        • Why the parameters: the statement closes over three of start()'s locals and nothing else. It has no arguments, super, new.target or bare this, and no return, await, break or continue at its top level (measured on the AST). The handler's own returns and awaits are inside the arrow and move with it.
        • The stub in start() is one block at the same position: the getter reads, one host literal and registerAuthzMiddleware.call(host, ctx, ql, metadata);. Registration stays synchronous and in the same place in start()'s order: after the security service registration, before the log line and the position-catalog middleware.
        • The host 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 11 applied it to one statement of start():
          • 24 methods as late-bound closures (addressedByIdWriteId, addressedPredicateWrite, assertControlledByParentWrite, callerCannotReadAddressedRow, composeWriteTenantWall, computeControlledByParentFilter, computeForbiddenFieldWrites, computeLayeredRlsFilter, computePartialMaskRules, computeQueryGuardFieldPerms, computeRlsFilter, computeWriteCheckFilter, delegatorOwnWriteFloorOptions, extractSingleId, getCallerPreImage, getObjectFieldNames, maskOperationResult, raiseWriteWallRefusal, readableRowsScopeForPredicateWrite, resolveCallerPosture, resolveOperationPrincipals, resolvePermissionSetsForContext, resolvePreImageFloorDrop, writeCheckFieldOptions);
          • engineOperationScope, declared readonly, by value (the same AsyncLocalStorage);
          • delegatedAdminGate, epoch, fieldMasker, logger, permissionEvaluator and ql, which are not, as getters, so each is read per operation as before.
        • It writes no member. No receiver changes: each field is an object whose own methods the handler calls, or a value it reads, and every method call goes through the host's closure to the plugin's method on the plugin.
      • Two module-level declarations only the handler reads move with it, docblocks included:
        • WriteImageCheckSeam (:404-:439). It gains export and nothing else, and security-plugin.ts imports it back type-only, because the staying WriteWallRowVerdict docblock links to it ({@link WriteImageCheckSeam}, :488). Cut 6's principalLessDenial import is the precedent.
        • userFacingDenialMessage (:1141-:1194), unchanged.
      • Import names only the moved code reads leave security-plugin.ts, 20 in all, and the module imports each from the specifier security-plugin.ts imports it from: recordNotFoundError (@objectstack/core), PUBLIC_FORM_SERVER_MANAGED_FIELDS (@objectstack/spec/security), markFilterSubtreeProvenance (@objectstack/spec/data), renderOperationMessage (@objectstack/spec/system), resolveEngineUpdateDispatch and the type EngineUpdateDispatchData (@objectstack/metadata-core), crossFieldClassRefusalCarriedBy and findCrossFieldClassRefusal (@objectstack/formula), assertFlowSystemIdentityWriteAllowed, intersectDelegatedScope, jsonColumnCheckRefusalCarriedBy, storedFormCheckJudge, compiledPolicyNameOf, intersectTenantLayer0Verdicts, rlsOperationForVerb, unresolvedPostureDenialMessage, unresolvedPostureLogLine, assertReadableQueryFields, MaskedValueWriteError and assertEngineOwnedWriteAllowed (each from its ./ module).
        • Seven comment lines describe three of those imports, and they move with them: [#7414] above renderOperationMessage, [#19989] above the metadata-core import, and the [#8220] paragraph above the @objectstack/spec/data import, which names markFilterSubtreeProvenance alone. The [#21829] paragraph under it describes MAX_BULK_PER_ROW_HOOK_ROWS, which stays, and it stays.
        • isGuestEnvelope and requiredCapsForOperation stay, exported, and the module imports them by value: other modules read both, and packages/spec/liveness/object.json anchors requiredCapsForOperation by symbol in that file. RlsFilterOptions keeps its one declaration; the module names it through the class's signature (NonNullable<Parameters<SecurityPlugin['computeLayeredRlsFilter']>[4]>), as cuts 9 and 10 did.
    • Readers that key on the moved lines, re-pointed in this PR. Each fails without its edit (measured with the edit reverted):
      • content/docs/permissions/system-context.mdx, rows 1, 2 and 30. R3 holds start()'s last isSystem read, the row-1 short-circuit, and the census keys it by file and enclosing symbol, so each row's security-plugin.ts#start becomes security-plugin-authz-middleware.ts#registerAuthzMiddleware. Reverted: [file-without-a-row] and [anchor-is-not-a-read-site].
      • scripts/check-system-context-census.mjs, the NON_READ_ANCHORS row that declares collapsesOntoRead for row 2's owner_id guard: file, symbol and the why text's symbol name change, and collapsesOntoRead: true and rowSeams stay. The ledger's docblock names the same pair as its worked example, so that one path changes too. Reverted: [ledger-stale-collapse].
      • docs/adr/0055-master-detail-controlled-by-parent.md:24, security-plugin.ts#rlsFilter → security-plugin-authz-middleware.ts#rlsFilter: the rlsFilter local is R3's (:4038 at base). This is the Tier H edit, in its own commit. Reverted: check-adr-symbol-anchors reports [unresolved-symbol].
      • Not readers here: the plugin-security: delegation probes and diagnostics (canReadObject / canWriteObject / canExport, field mask, explain) answer for an on-behalf-of context the operation prologue now refuses (PR #25101) #25107 pin (R3 resolves no delegator: 0 asks, 0 resolutions), the core canReadObject caller pin (R3 has no call), the position-name fold census (its method stays).
    • scripts/adr-anchors/: no new entry, and the security-plugin.ts entry is unchanged and stays true. Paragraph by paragraph:
      • ADR-0055 §1 is the relation reader (security-plugin-cbp-relation.ts). ADR-0106 D7 is the field readers (cut 9's module). ADR-0138 D1 names the seed claim (cut 11's module); R3's guest-owner branch cites ADR-0138 D1, but the paragraph is about the claim's re-owning arm.
      • ADR-0123 D2 names "step 3.7's no-active-organization refusal" and its order ahead of the forge guard. The refusal, its text and its pre-test live in security-plugin-write-tenant-wall.ts, which carries that paragraph in its own entry since cut 5. R3's step 3.7 asks that module and takes wall.preTest before wall.judgePayload(...), and only for insert/update. Disclosed: that call-site order now sits in a module with no entry. An ADR-0123 entry for it would need an ADR-0123 citation the moved code does not carry, and this cut plants none. The seat may want this as a follow-up.
    • Decision letters. check:adr-anchors counts them only on the anchored surface (84 files at both ends). On the merge-base 0fab7baf51: 1959. At the head: 1908. All −51 are R3's letters leaving security-plugin.ts, which holds 236 such citations at base and 185 at head: ADR-0090 D10 ×20, ADR-0138 D1 ×6, ADR-0096 D5 ×4, ADR-0066 D2 ×3, ADR-0058 D4 ×2, ADR-0066 D3 ×2, ADR-0090 D12 ×2, ADR-0105 D5 ×2, and ×1 each ADR-0029 D8, ADR-0056 D2, ADR-0057 D1, ADR-0058 D6, ADR-0095 D1, ADR-0111 D3, ADR-0131 D8, ADR-0139 D1, ADR-0139 D5.1 and ADR-0139 D6. Three letters leave security-plugin.ts entirely: ADR-0058 D6, ADR-0105 D5 and ADR-0131 D8. No letter is planted and none is invented. Repo-wide citations go from 52,274 to 52,276: the module header names ADR-0056 and ADR-0090 D12 once each, describing the handler's steps.
    • Every other 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.
    • .d.ts: ruling 6348891160 item 1, with the same four-hash block, normaliser, raw line count and positive control as cuts 1 to 11. On full builds at both ends, dist/index.d.ts and index.d.mts are byte-identical to base (5e7e8df6…, 14,696 lines). The positive control (private removed from epoch, real rebuild) gives f54773ce… / 3d05f7fb…, and the restored rebuild gives 5e7e8df6… again.
    • No changeset (skip-changeset).
    • ⛔ Not touched: R1, R2's stub, R4, init(), every other class member, and any behaviour.

    Anchor and reader reading before the cut (ruling item 2), on 0fab7baf51, with the cut in place:

    • ADR anchors into security-plugin.ts or the new module, mapped with extractAnchors and symbolSegmentResolution from scripts/symbol-anchors.mjs: 27 citing lines over 18 symbols. One changes path, #rlsFilter (ADR-0055:24): declaration at security-plugin.ts:4038 (start) at base, declaration at security-plugin-authz-middleware.ts:2051 (registerAuthzMiddleware) at head. Every other one stays declaration or literal in security-plugin.ts, on the same member.
    • node scripts/check-adr-symbol-anchors.mjs exits 0 at both ends (2,384 anchors over 142 records).
    • Censuses, compared site by site with the module path mapped back to security-plugin.ts and registerAuthzMiddleware back to start:
      • isystem-census: 149 sites at both ends, 0 differences. The one moved read is the row-1 short-circuit (:2297 at base, security-plugin-authz-middleware.ts:310 at head).
      • check-system-context-census exits 0 at both ends: 149 sites in 77 files, 129 symbols, 142 cited against 142 required, 164 anchors.
      • collapsesOntoRead, at both ends: the census finds exactly one elevation read in the ledger row's symbol, and the row declares the overlap. Base: row security-plugin.ts#start, 1 read in security-plugin.ts#start. Head: row security-plugin-authz-middleware.ts#registerAuthzMiddleware, 1 read there, and 0 in security-plugin.ts#start.
      • tenant-audit-census: 238 sites at both ends, 0 differences, 176 non-engine calls.
    • Bundle order, disclosed. Nine module chunks are emitted later in index.mjs, because security-plugin.ts no longer imports them first: flow-system-identity-write-refusal.ts, explain-engine.ts, lifecycle-verb-rls-operation.ts, rls-compiler.ts, object-default-row-scope.ts, tenant-layer.ts, declared-comparison-columns.ts, rls-check-stored-form.ts and predicate-guard.ts. None imports security-plugin.ts. Their top level only declares: constants, sets, WeakMaps, a WeakSet and symbols. The only imported values it reads are three @objectstack/spec constants, from outside the bundle. Both built bundles load (import() and require), with the same 66 exports and the same 17 / 24 external specifiers at both ends.

    Serial check: of the 10 open PRs, two touch security-plugin.ts, both from another project:

    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 to services seat 1 for the re-measure and ACCEPT, then to the human-merge route.
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: 6375175505
    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

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratedomain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedenhancementNew feature or requestpm:dispatchedpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions