Skip to content

refactor(plugin-security): move SecurityPlugin's leaf readers into security-plugin-object-inputs.ts (#25223 cut 1) - #25290

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-3fzya4
Oct 22, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-3fzya4

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

Part of #25223
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160

Cut 1 of the security-plugin.ts split. The bodies of the 16 leaf readers of SecurityPlugin move, unedited, to the new module packages/plugins/plugin-security/src/security-plugin-object-inputs.ts. These are the per-object inputs every gate layer reads: the row-level security policy collection with its ADR-0105 D3 admission and the authored-tenant-policy warning, the unresolved-posture probes, the field-level masks and required-permission folds, and the field-name tables. The one module-level declaration only those bodies read, isDelegatedPrincipalLeg, moves with them, and so do the four imports only they used.

It is move-only, and the card stays open for the later cuts. The seam plan for the series is in the claim comment on #25223 (6349703046); each later cut is re-measured on the merged file first. Branch: claude/project-thread-3fzya4, the harness-pinned branch for this session; it names no issue, so the claim comment is the identity record.

The PR also carries the one-sentence rider this card owes: a docblock note in controlled-by-parent-unwritable-master-write.test.ts saying why the raw registration door alone pins the resolution (ruling 6345869077 item 6, owed by the services seat's ACCEPT 6348830068 on PR #25256). Comment only.

The published .d.ts does not change

Under the series bar (ruling 6348891160 item 1), measured on base 72fad922 and head a7cd7c62:

  • Raw sha256 of dist/index.d.ts and dist/index.d.mts, base and head, all four the same: 61c36a53c30dfccdd4a62280b922bfd1328c83e02253266c469f809c0a78679c. The raw diff is 0 lines, so no union order moved either.
  • Normalised sha256, all four the same: 942a52dc2afc950fdc2f89a720b08d4a8c584250f940db05d099c98f813c2359.
  • The normaliser, run from packages/plugins/plugin-security (it sorts the members of each string-literal union and changes nothing else):
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
  • Positive controls on a copy of the head dist/index.d.ts, line 2410 (private collectRLSPolicies;):
    • Made public (collectRLSPolicies;): raw 05654fc4ea1aa556ce256aa3bee1f94c6ec9b5ae1a240c7744e6fe7225d8cd43, normalised 9280d1c333e070fad6635d3adbb9dad308a9ad49a3e3d30cdf90002947f3f738.
    • Renamed (private collectRLSPoliciesRenamed;): raw f20c387525fecc3b2c65bd6113a3c83cfe0388d4defabfc0a5a0fb2149c449db, normalised 312949d507e1943df1f357c43a6a85d97fe8205aefea057ded1bf17866943ef7.
    • Both differ from the real hashes, so the comparison does see a visibility change and a rename.
  • Export tables: the ESM and CJS bundles each export the same 63 names at base and head, loaded at runtime rather than parsed. The sorted name list hashes f607fd765d6b277e08b3b46ccb16d1e2d133ec55658ffe40155aef25e1bf54f0 on all four.
  • The bundle JS bytes differ (index.js aba80183 to b52cd4f1, index.mjs aca41ade to cd55afa9), because code moved within them.

No changeset: skip-changeset, per ruling 6348891160 item 4.

What moved

Base line numbers are each method's signature at 72fad922. The last column is the members the body reads, which is exactly what its host carries.

method base lines moved function async host members
collectRLSPolicies :10366-:10400 no admitCollectedPolicy, ownershipFloorAlternates, ql, rlsCompiler
admitCollectedPolicy :10419-:10427 no orgScopingEnabled, warnAuthoredTenantPolicyOnce
warnAuthoredTenantPolicyOnce :10437-:10450 no logger, warnedAuthoredTenantPolicies
resolveUnresolvedCause :10579-:10582 yes probeMetadataOutage, probeUnpublishedDraft
probeMetadataOutage :10628-:10639 yes metadata
probeUnpublishedDraft :10673-:10684 yes ql
computePartialMaskRules :10708-:10727 no permissionEvaluator
computeQueryGuardFieldPerms :10753-:10773 no computePartialMaskRules, foldFieldRequiredPermissions, permissionEvaluator
computeForbiddenFieldWrites :10788-:10804 no fieldMasker, foldFieldRequiredPermissions, permissionEvaluator
computeReadPartialMaskRules :10826-:10840 no computePartialMaskRules
maskOperationResult :10856-:10885 no computeReadPartialMaskRules, fieldMasker, foldFieldRequiredPermissions, permissionEvaluator
foldFieldRequiredPermissions :10887-:10902 no permissionEvaluator
getObjectFieldNames :10908-:10924 yes fieldNamesCache, loadObjectFieldNames
writeCheckFieldOptions :10943-:10952 yes metadata, ql
loadObjectFieldNames :10954-:11015 yes datetimeFieldNamesCache, numberComparandFieldsCache, tenancyDisabledCache
extractTargetField :11035-:11053 no none (called directly)

Plus isDelegatedPrincipalLeg (base :471-:487, with its docblock), read only by collectRLSPolicies. Left in the class on purpose: getObjectSecurityMeta, which sits among these methods, because packages/spec/liveness/object.json anchors it next to normalizeRequiredPermissions and that file belongs to another lane.

The shape (#23050 shape B, as #25222 and #25224 apply it)

  • The class keeps each private method with its name, signature, docblock and position, because tsup emits private members with their JSDoc into the .d.ts. Each body is now one host literal plus fn.call(host, …), marked [#25223 cut 1].
  • ObjectInputsHost types every member as the class member itself (SecurityPlugin['m'], an indexed access that works on private members). A renamed or retyped member is a compile error. There is no cast, and no member becomes public or protected. The module imports SecurityPlugin type-only, so there is no runtime import cycle.
  • Each moved function types its this parameter as the Pick of ObjectInputsHost over exactly the members its own body reads, and each stub annotates its host literal with the same Pick. A missing or extra member is a compile error, and the closures get their parameter types from it.
  • Methods go in as late-bound closures that call this.m(...a) on the plugin at call time. A spy or test double set on the plugin is still the one the body reaches, and the moved bodies that call one another do so through the plugin, as before. Every this.m( call in the module goes to one of the eight methods a host carries this way; no moved body calls a function-valued field, so no receiver changes.
  • Each field was classified on its own declaration, one rule for all. A field declared readonly (the four caches, ownershipFloorAlternates, warnedAuthoredTenantPolicies) goes by value, because it is never reassigned and the Map or Set is the same object. Every other field (ql, metadata, logger, rlsCompiler, permissionEvaluator, fieldMasker) and the orgScopingEnabled accessor goes as a getter, so each read happens when the body makes it, after any await, as before. start() assigns ql, metadata and logger after construction, and tests replace permissionEvaluator on a constructed plugin.
  • The host declares every field readonly, so a moved body cannot assign a member through it; the typecheck passes, so none does.
  • Six methods lose async: resolveUnresolvedCause, probeMetadataOutage, probeUnpublishedDraft, getObjectFieldNames, writeCheckFieldOptions and loadObjectFieldNames. Each returns the moved async function's own promise. The stub cannot throw before it returns that promise: building the host reads only readonly fields and defines getters without running them, and calling an async function never throws synchronously. So a caller awaits a promise that settles exactly as the old method's did.
  • extractTargetField reads no member, so its method calls it directly.
  • The moved bodies keep saying this., so the moved lines are unedited. Nothing re-exports the module, so the package entry does not move.

Proof of move-only

  • Bodies, by AST token: for all 16 methods, the moved function's body has the same TypeScript token sequence as the base method body, and each of its 234 lines is the base line with two leading spaces removed. Parameters, return types and async match. A negative control, one token changed in one body, fails the check.
  • Class side: each method keeps its docblock and signature, with async dropped on the six above. isDelegatedPrincipalLeg is verbatim (17 lines) in the module and absent from security-plugin.ts.
  • git's own view (git diff --color-moved=plain --color-moved-ws=allow-indentation-change 72fad922 HEAD over the two source files): 255 of 262 deleted lines and 318 of 621 added lines are detected as moved.
    • Deleted, not moved (7): the six private async signature lines, which lost async, and the withOwnershipFloorAlternates, name taken out of a multi-name import.
    • Added, not moved: the stub bodies, the import of the new module with its comment, and in the module the license header, the module docblock, the imports, ObjectSecurityMeta (a type alias over getObjectSecurityMeta's return type, so security-plugin.ts needs no new export), ObjectInputsHost, one pointer docblock per function and the rewritten signature heads. The rest are lines git does not pair with a moved block, such as blanks and closing braces.
  • Size: 890 changed lines (+628 / -262, 3 files), under the 3000-line human-merge threshold. security-plugin.ts goes from 11,054 to 10,929 lines; it shrinks only by the bodies because every docblock stays for the .d.ts.

Readers

No test, script, gate, baseline or docs anchor reads the moved lines by path, so nothing is re-pointed.

  • The symbol-anchor gates still resolve every anchor into this file: pnpm check:adr-anchors, check-adr-symbol-anchors (2383 anchors), check-scripts-symbol-anchors, check-spec-docblock-symbol-anchors, pnpm --filter @objectstack/spec check:liveness, and check-platform-checklist --changed-from origin/main (6 anchors judged, 0 lost). All exit 0. The ADR anchor for this file names resolveCbpRelation, getMetadataReadableFields, claimSeedOwnershipOnSettle and readSeededRecordIds, none of which moved.
  • Line citations: no security-plugin.ts:N citation in the repo points into a moved range (base :471-:487 and :10366-:11053). Base lines :56-:470 move by between -1 and +19 (the import block changed), lines from :10373 on move up, and lines :489-:10372 keep their numbers. Three citations land in the shifted head range: CHANGELOG.md (:303, release history), docs/launch-readiness.md (:309) and packages/spec/liveness/object.json (:132, inside a note that records it as an already-repointed stale cite). At base all three already point at unrelated lines, so this cut does not make any of them wrong.
  • Census gates run on base and head, compared. These have identical output: check-platform-object-tenancy-census, check-position-name-fold-loaders, check-registry-log-declared, check-rest-log-spy-declared, check-system-context-census, check-tenant-audit-census, check:cross-package-test-inputs, check:doc-authoring, check:driver-memory-census, check:issue-citations and check:query-options-erasure. Only the scanned counts move, by the one new file: check-undeclared-dep-imports (3284 to 3285 files, 2817 to 2820 specifiers), check:logger-receiver-detach (3600 to 3601 files) and check:org-identifier (3626 to 3627 files).

Acceptance notes

  • Nothing outside this cut is touched: no other section of the class, no behaviour, no other lane's file.
  • The test file gains comment lines only.

Gates

All at head a7cd7c62, on base 72fad922. Between that base and the current origin/main (1eb1afae), no file under packages/plugins/plugin-security/ changed.

  • pnpm --filter @objectstack/plugin-security test (vitest run --maxWorkers=3, under the verify lock): Test Files 210 passed (210), Tests 4379 passed, 45 skipped (4424). Base 72fad922, in a separate worktree: the same counts. VERDICT command-exit 0 on both.
  • pnpm --filter @objectstack/plugin-security typecheck: exit 0 (tsc --noEmit, the scripts tsconfig, and check:test-typecheck with 0 files / 0 errors in the debt ledger).
  • eslint --no-inline-config over the three touched files: exit 0.
  • pnpm --filter @objectstack/plugin-security build: exit 0, which produced the .d.ts and bundles measured above.
  • dispatch-gates --commands: 58 commands, 890 changed lines, under the threshold. --ran reconciliation: 58 derived, 58 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero: all 58 recorded an exit code and none is 3).

Per command (exit code):

  • node scripts/check-ci-filter-parity.mjs exit 0
  • node scripts/check-closing-keyword-parity.mjs exit 0
  • node scripts/check-closing-keyword-parity.mjs --self-test exit 0
  • node scripts/check-comment-mask-adoption.mjs exit 0
  • node scripts/check-comment-mask-adoption.mjs --self-test exit 0
  • node scripts/check-comment-mask-corpus.mjs exit 0
  • node scripts/check-dts-emitted.mjs --self-test exit 0
  • node scripts/check-issue-citations.mjs exit 0
  • node scripts/check-keyed-text-bounds.mjs exit 0
  • node scripts/check-keyed-text-bounds.mjs --self-test exit 0
  • node scripts/check-platform-object-tenancy-census.mjs exit 0
  • node scripts/check-platform-object-tenancy-census.mjs --self-test exit 0
  • node scripts/check-plugin-teardown-shape.mjs exit 0
  • node scripts/check-plugin-teardown-shape.mjs --self-test exit 0 (the first run exited 3: this shallow clone lacked the pinned fixture commit; it passed after git fetch --depth=1 of that commit)
  • node scripts/check-position-name-fold-loaders.mjs exit 0
  • node scripts/check-position-name-fold-loaders.mjs --self-test exit 0
  • node scripts/check-registry-log-declared.mjs exit 0
  • node scripts/check-registry-log-declared.mjs --self-test exit 0
  • node scripts/check-rest-log-spy-declared.mjs exit 0
  • node scripts/check-rest-log-spy-declared.mjs --self-test exit 0
  • node scripts/check-system-context-census.mjs exit 0
  • node scripts/check-system-context-census.mjs --self-test exit 0
  • node scripts/check-tenant-audit-census.mjs exit 0
  • node scripts/check-tenant-audit-census.mjs --self-test exit 0
  • node scripts/check-undeclared-dep-imports.mjs exit 0
  • node scripts/check-undeclared-dep-imports.mjs --self-test exit 0
  • node scripts/docs-audit/check-affected-docs.mjs exit 0
  • node scripts/docs-audit/check-drift-comment.mjs exit 0
  • pnpm --filter @objectstack/spec run check:duration-unit-keys exit 0
  • pnpm check:closed-ceremony-text exit 0
  • pnpm check:cross-package-test-inputs exit 0
  • pnpm check:doc-authoring exit 0
  • pnpm check:driver-memory-census exit 0
  • pnpm check:dts-closure exit 0
  • pnpm check:dual-build-cjs-loads exit 0 (the first run exited 3, PREREQUISITE NOT MET, before a full build; after a full pnpm exec turbo run build under the verify lock (77 of 77 tasks), it passed: 112 entry points across 69 packages load)
  • pnpm check:engine-double-contract exit 0
  • pnpm check:error-status-conformance exit 0
  • pnpm check:gitlink-declared exit 0
  • pnpm check:i18n exit 0 (the same: exit 3 before the full build, exit 0 after it)
  • pnpm check:i18n-stale-fill exit 0
  • pnpm check:issue-citations exit 0
  • pnpm check:lean-entry-closure exit 0
  • pnpm check:logger-receiver-detach exit 0
  • pnpm check:nul-bytes exit 0
  • pnpm check:objectql-double-limit exit 0
  • pnpm check:org-identifier exit 0
  • pnpm check:page-declaration-shape exit 0
  • pnpm check:published-files exit 0
  • pnpm check:query-options-erasure exit 0
  • pnpm check:refd-timer-probe exit 0
  • pnpm check:slot-lookup exit 0
  • pnpm check:sourcemap-no-sources-content exit 0
  • pnpm check:test-source-alias exit 0
  • pnpm check:tier-file-adoption exit 0
  • pnpm check:type-check-coverage exit 0
  • pnpm check:type-check-debt exit 0
  • pnpm check:watch-hint-literal exit 0
  • pnpm check:where-matcher exit 0

Generated by Claude Code

…curity-plugin-object-inputs.ts (wip)

The bodies of sixteen private methods (the RLS policy collection and its
admission, the unresolved-posture probes, the field-level masks and
required-permission folds, the field-name tables) move unedited, re-indented
one level, with isDelegatedPrincipalLeg, which only they read. The class
keeps every method, its signature and docblock, and hands each body a host
literal of exactly the members it reads.

Claude-Session: https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa
Co-authored-by: Claude <noreply@anthropic.com>
…he unwritable-master resolution (wip)

Claude-Session: https://claude.ai/code/session_017BoLT8Gaa8t9a7AWVH8qZa
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 22, 2026
@objectstack-fleet
objectstack-fleet Bot requested a review from os-sales October 22, 2026 20:31
@github-actions github-actions Bot added the tests label Oct 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-security, touching 54 documentable anchor(s).

31 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 1eb1afae071530f66f4d93d416e7f452ceab3de3.

⛔ 10 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 16 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 1eb1afae071530f66f4d93d416e7f452ceab3de3 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 0486b94fa4c9e11a404defd26dcf5816b90ece45 — the merge of head a7cd7c6258a8d8eb81624ba0e05fba79ef8dbe23 into base 1eb1afae071530f66f4d93d416e7f452ceab3de3, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0486b94fa4c9e11a404defd26dcf5816b90ece45 && git checkout 0486b94fa4c9e11a404defd26dcf5816b90ece45
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1eb1afae071530f66f4d93d416e7f452ceab3de3 a7cd7c6258a8d8eb81624ba0e05fba79ef8dbe23 && git checkout -B drift-repro 1eb1afae071530f66f4d93d416e7f452ceab3de3 && git merge --no-ff a7cd7c6258a8d8eb81624ba0e05fba79ef8dbe23

node scripts/docs-audit/affected-docs.mjs --json 1eb1afae071530f66f4d93d416e7f452ceab3de3

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 1eb1afae071530f66f4d93d416e7f452ceab3de3 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · 2026-10-22T21:08Z

domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · card #25223 (cut 1; claim 6349703046, Ruling-ref: 6348891160; card session branch claude/project-thread-3fzya4) · reviewed head a7cd7c6258a8d8eb81624ba0e05fba79ef8dbe23

  • Shape: a move. Sixteen SecurityPlugin leaf-reader bodies move unedited into security-plugin-object-inputs.ts by the landed [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 pattern (shape B: a Pick host, readonly fields by value, the rest and orgScopingEnabled by getter; six stubs drop async and return the original promise). 3 files, +628 / −262.
  • Ruling 6348891160 (.d.ts criterion), checked by the seat: the report block carries base and head raw and normalised sha256, the normaliser verbatim, a raw diff of 0 lines and two positive controls. The seat re-ran it once on this head from its own build (turbo run build --filter=@objectstack/plugin-security under the verify lock, exit 0): dist/index.d.ts and dist/index.d.mts both raw 61c36a53…, normalised 942a52dc…, equal to the report's values. The private → public control on collectRLSPolicies normalises to 9280d1c3…, different, as reported. The CJS entry exports 63 names, as reported.
  • Ruling 6345869077 item 6 (carried from fix(plugin-security)!: under a controlled_by_parent declaration whose one master reference is readonly or system, every write that sets it is refused (ADR-0055 §1) #25256's ACCEPT 6348830068): controlled-by-parent-unwritable-master-write.test.ts now says why the raw registration door alone pins the resolution. That deviation is closed.
  • Changeset: skip-changeset, no changeset; no new gate or script.
  • Declarations: Clause-②: no, Metadata-protocol: none, Part of #25223 (the card stays open for cut 2). No contract-review record is owed for a pure move with an unchanged published surface.
  • Gates: dispatch-gates --ran 58 / 58; plugin-security 210 files / 4379 pass, the same as base; every check-run on this head success or an expected skip.
  • Landing: not governed (check-governed-merges --pr 25290: 890 lines). Ready + auto-merge through the relay. Cut 2 (the cbp read derivation, with its ADR anchor move) restarts from main after this merges.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 22, 2026 21:08
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 22, 2026 21:08
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 22, 2026
Merged via the queue into main with commit f47f7f2 Oct 22, 2026
47 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-3fzya4 branch October 22, 2026 21:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants