Skip to content

feat(plugin-sharing): list the objects a sharing rule can take effect on - #31207

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-txk3tb
Nov 7, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-txk3tb

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by 田騏 · project thread

Refs #30209 (row 5 B, server half; rows 1 and 2 measured below). The card stays open for row 1 and the console picker.

Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

  • 改了什么:plugin-sharing 新增只读接口 GET /api/v1/sharing/shareable-objects,返回「共享规则能生效的对象」清单(业务对象在前、系统对象在后,各组按标签排序);判定直接调用保存时拒绝用的同一个判断(A sharing rule on an object without record sharing saves and silently does nothing #30621 的 sharingRuleObjectRefusal),不另写一份;会话、账号、API 密钥、密钥等凭据类对象都不在清单里。
  • 为什么改:新建共享规则时对象选择器列出 86 个对象,大多是 sys_*,业务对象被埋住,还能选到存凭据和会话的对象(保存才报错)。按裁决 6670358877 的第 5 行 B,服务端先给出清单,控制台下一张卡据此只列这些对象。
  • 风险与回滚:纯新增、只读,不改存储、不改任何已有接口和元数据;只有共享规则管理员(manage_sharing,或旧的 manage_platform_settings,且有当前组织)能调用,其他人 403、匿名 401、带查询参数 400。回滚即撤销本 PR。
  • 先看:packages/plugins/plugin-sharing/src/shareable-objects.ts(清单与排序,判定由调用方传入)、src/sharing-rule-service.ts 的 listShareableObjects(准入与判定接线)、src/shareable-objects-routes.ts(拒绝顺序:身份→受限登录→匿名→查询参数→管理员)。
  • 两分钟验证:pnpm --filter @objectstack/plugin-sharing exec vitest run src/shareable-objects.test.ts,预期 16 个用例全过,其中「每个不在清单里的对象,defineRule 都以 SHARING_NOT_ENABLED 拒绝」逐个对象核对。
  • 需要你做的:无,席位落地。

What changes

  • A read-only list, mounted by plugin-sharing itself. registerShareableObjectsRoutes (src/shareable-objects-routes.ts) mounts GET /api/v1/sharing/shareable-objects on http.server (alias http-server) at kernel:ready, the way the share-link routes are mounted. The rule administration routes (/api/v1/sharing/rules*) belong to @objectstack/rest over the sharingRules spec contract; a list there would need a new contract member, so this door reads the plugin's own SharingRuleService instead and touches neither packages/rest nor packages/spec. No HTTP server: one warn naming the route.
  • What is listed (src/shareable-objects.ts): every object in the engine's registry (getConfigs()) for which SharingService.sharingRuleObjectRefusal(object) answers null, the check both save doors (A sharing rule on an object without record sharing saves and silently does nothing #30621) already ask, handed in as a function and never restated. Each entry is { name, label, system }; system is isSystem or a sys_ name. Business objects first, then platform objects, each group by label, then name. Response: { success: true, data: { objects } }.
  • Who may ask (SharingRuleService.listShareableObjects(context), new): the sharing-rule surface's own admission, assertCanManageRules (ADR-0111 D6: manage_sharing or the legacy manage_platform_settings, plus an active organization unless platform authority). The route's refusal order, each before the list is read: caller resolution through resolveRequestExecutionContext (session or API key; a permission-store outage and an API key lifecycle refusal relayed as declared), held sign-in 403 with the policy code, anonymous 401 UNAUTHENTICATED, any query parameter 400 VALIDATION_ERROR naming it (closed set: empty), then a non-administrator 403 PERMISSION_DENIED with the surface's sentence.
  • Never an empty list in place of an answer: no rule service running (enforce: false) answers 501 NOT_IMPLEMENTED, as the REST rule routes do; an engine that cannot enumerate its objects answers 500 INTERNAL_ERROR with the error in the fault log.
  • Ledger and guards: src/sharing-route-ledger.ts (one server-only row) with its package-local conformance test; the live-mount parity dogfood test imports it, and scripts/cross-package-test-inputs.mjs / turbo.json declare it as that test's input; scripts/check-route-envelope.mjs declares the new door in MODULES at { responses: 0, ok: 0, err: 0 } (every body goes through sendOk / sendError / sendServerFault). These four one-line wirings are the ones feat(service-messaging): serve the notification topics and channels a preference or template can name #31004's amendment named.
  • One spelling of "platform object": isPlatformObjectSchema (isSystem or a sys_ name) is extracted in src/sharing-service.ts from effectiveSharingModel's own test, and the list reuses it for its system flag (no behaviour change; the system-context census keys that read to this file).
  • Changeset: .changeset/30209-sharing-shareable-objects.md, @objectstack/plugin-sharing: patch, both declaration lines at line start.

Row 5's security note: credential and session objects

Measured, then pinned. The platform's credential and session objects as their packages declare them (sys_session, sys_account, sys_verification, sys_api_key, sys_two_factor, sys_oauth_application, sys_oauth_access_token, sys_oauth_consent, sys_jwks, sys_secret) are none of them listed, and defineRule refuses a rule on each with SHARING_NOT_ENABLED / 422 (each is owner-less or public). So, after #30621, a rule on one of them is refused at save and never shares anything; none remains listable. A wider probe registered every object definition @objectstack/platform-objects (root, identity, security, audit) and plugin-sharing export, 65 objects: none was listable.

Rows 1 and 2: readings (no code)

Row 2 is done on main 8ef4d309 (PR #30496), both halves:

  • Condition in words: describeFilterGroup in ui/packages/fields/src/widgets/FilterConditionField.tsx, 2 hits; its pin FilterConditionField.readWords-30209.test.tsx (3 cases) is on main.
  • Recipient by name: the record page hands the record to the field face (dependentValues={data} in ui/packages/plugin-detail/src/DetailSection.tsx, 2 sites), so recipient-picker reads recipient_type; pinned by recordFace.siblingValues-30209.test.tsx (2 cases) on main.

Row 1: the cheapest honest shape, and which arm needs spec.

  • The tab today is record:related_list over sys_user_permission_set (packages/platform-objects/src/pages/sys-user.page.ts), which can only list stored rows: sets from the default (everyone) binding and from positions are never rows, so no related-list source can show them. No existing page component shows effective access.
  • The page component needs no spec change. The spec's component type is an open string outside the namespaces its enum claims (ai, app, element, global, nav, page, record), and mcp:authorized-assistants on the same page is the precedent: a console widget whose zod arm lives in ui/packages/types, placed by the page with no packages/spec edit. A security:effective-access widget (any unclaimed namespace) follows it. The ui seat's reading (6659268469) that row 1 needs a spec vocabulary entry holds only for a type inside a claimed namespace such as record:.
  • The data is the missing piece, and that arm is server. POST /api/v1/security/explain answers one object and one operation; its principal.permissionSets lists the resolved names without their origin, and only its per-layer contributors[].via says how a set arrived (baseline, everyone, position:NAME), per object. resolveUserAuthzGrants (@objectstack/core) carries permissions and directPermissions but not which position or default binding brought each other set. So "effective sets with their origin (default set, position, direct)" needs a per-user read, for example GET /api/v1/security/users/:id/effective-access, built on the resolver plus the catalog's position bindings. If it is served by @objectstack/rest over a spec service contract, that contract member is the one spec arm; served by plugin-security on IHttpServer (this PR's shape), none.
  • Proposed split for the seat: (1) server card: the per-user effective-access read with origins; (2) ui card: the widget plus the one-line page edit in sys-user.page.ts (platform-objects). No spec card unless the read goes through the REST contract.

Tests

  • src/shareable-objects.test.ts (new, 16 cases) on real ObjectQL + SqlDriver (better-sqlite3), with the real SharingService / SharingRuleService and the A sharing rule on an object without record sharing saves and silently does nothing #30621 save guard bound:
    • the list: admitted objects only, business before platform, each by label; four admitted shapes (private, read, public-with-owner, a platform object under enforcement) and four refused (owner-less, the plugin's bypass sys_user, controlled_by_parent, sys_sharing_rule itself);
    • one verdict: for every registered object, "not listed" equals "defineRule refuses with SHARING_NOT_ENABLED 422";
    • the credential and session objects above: absent, and each refused at defineRule;
    • admission: a member without manage_sharing and an organization-less manage_sharing holder are refused PERMISSION_DENIED; CONTROL the legacy manage_platform_settings is admitted;
    • the door, through the real platform resolver (a fake auth session, a grant store bound to a catalog whose set confers manage_sharing): an administrator is served the list and the service receives the resolved context; a member is refused 403 PERMISSION_DENIED by the real service; anonymous 401, held sign-in 403 PASSWORD_EXPIRED, a query parameter 400 naming it, each asserting the envelope and that the list was never read; 501 when no rule service runs and 500 when the list cannot be read, neither with data.
  • src/sharing-route-ledger.conformance.test.ts (new): registrar enumeration against the ledger, both directions.

Acceptance notes

Verification

All readings on head ``59c76e787e (59c76e7`)` unless a line says otherwise.

Package suite, typecheck, lint
pnpm --filter @objectstack/plugin-sharing test :: exit 0   (Test Files 70 passed, Tests 1464 passed)
pnpm --filter @objectstack/plugin-sharing typecheck :: exit 0   (tsc, scripts tsc, check:test-typecheck OK, debt unchanged)
pnpm --filter @objectstack/dogfood exec vitest run test/route-ledger-live-mount-parity.dogfood.test.ts :: exit 0   (8 passed; log: "shareable objects list mounted at GET /api/v1/sharing/shareable-objects")
node node_modules/eslint/bin/eslint.js --no-inline-config <the 11 touched .ts/.mjs files> :: exit 0

(The package has no lint script; the root eslint ran over the touched files.)

Negative-pin ablations (each through scripts/ablation-replace.mjs; anchor hit once; restored blob == HEAD, git diff HEAD empty)
  • Verdict filter deleted (if (refusal(name) !== null) continue;): 4 failed / 12 passed: the list case, the one-verdict case, the credential case and the door's administrator case.
  • Admission removed from listShareableObjects (this.assertCanManageRules(context) replaced by void context): 3 failed / 13 passed: member refused, organization-less refused, and the door's member 403. (A first attempt replaced the call with text the anchor already contained; the tool refused it as a no-op (replacement count moved 1 -> 1) and restored; rerun with the replacement above.)
  • Anonymous branch replaced by if (false as boolean) {: 1 failed (refuses an anonymous caller 401 UNAUTHENTICATED).
  • Query refusal replaced by if (false as boolean) {: 1 failed (refuses a query parameter 400 VALIDATION_ERROR naming it).
  • Business-before-platform comparison deleted: 3 failed (the list order, the platform classification order, the door's list).
Derived gates
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: 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-declaration-mirrors.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs --self-test :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/check-self-test-wired.mjs :: exit 0
node scripts/check-self-test-wired.mjs --self-test :: exit 0
node scripts/check-self-test-workflow-commands.mjs :: exit 0
node scripts/check-self-test-workflow-commands.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-whole-set-label-write.mjs :: exit 0
node scripts/check-whole-set-label-write.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/ci/snapshot-next.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
node scripts/pm/bare-root-worklist.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm --filter @objectstack/spec run check:empty-state :: exit 0
pnpm --filter @objectstack/spec run check:liveness :: exit 0
pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0
pnpm --filter @objectstack/spec run check:variant-docs :: exit 0
pnpm check:agent-test-spelling :: exit 0
pnpm check:bash32-floor :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:cli-command-ids :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: 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
pnpm check:engine-double-contract :: exit 0
pnpm check:entry-guard :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:i18n :: exit 0
pnpm check:i18n-stale-fill :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: 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:parse-guard :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:pm-dispatch-gates :: exit 0
pnpm check:pnpm-filter-targets :: exit 0
pnpm check:pnpm-flag-spellings :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:ratchet-remedy-authority :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:route-envelope :: exit 0
pnpm check:scripts-symbol-anchors :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:turbo-task-graph :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:vouch-for-reference-callers :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0

Reconciliation: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran <the list above> :: exit 0 (89 derived, 89 run, 0 NOT-MEASURED, 0 UNRUN).

Not derived, run as a check on the merged tree: pnpm --filter @objectstack/spec run check:generated exits 1 under the default 2 GB heap because tsc -p tsconfig.test.json aborts out of memory ("failed without diagnostics", exit 134), and exits 0 with NODE_OPTIONS=--max-old-space-size=8192 (CI sets 4096). This diff touches no packages/spec file.

🤖 Generated with Claude Code

https://claude.ai/code/session_017ihL2f62c1GYk6XzbdtCa4

GET /api/v1/sharing/shareable-objects answers the objects the save doors
admit a sharing rule on (SharingService.sharingRuleObjectRefusal), business
objects first, for Setup's sharing-rule object picker. Admitted as the
sharing-rule surface admits (manage_sharing, ADR-0111 D6).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ihL2f62c1GYk6XzbdtCa4
…s ledger

The list is compared with defineRule's own verdict for every registered
object; the platform's credential and session objects are pinned absent and
refused; the door is driven through the platform resolver. The ledger is
wired into the live-mount parity gate and the envelope guard.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ihL2f62c1GYk6XzbdtCa4
effectiveSharingModel's isSystem-or-sys_ test becomes isPlatformObjectSchema,
which the shareable-objects order reads too, so the object-schema isSystem read
stays where the system-context census already classifies it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ihL2f62c1GYk6XzbdtCa4
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 7, 2026
@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

35 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 de3996bd55e3394c60da31d346df70d0b7465e14.

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

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: PERMISSION_DENIED (literal, 51 pages)
  • 10 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 63 of 226 client-bound route-ledger rows — the other 163 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 163: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 59 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 104 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 — 11 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 de3996bd55e3394c60da31d346df70d0b7465e14 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from b893919ac673848622942d246657c4cf4e4fb856 — the merge of head 59c76e787e7b21dd493272fdd133042ed846a902 into base de3996bd55e3394c60da31d346df70d0b7465e14, 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 b893919ac673848622942d246657c4cf4e4fb856 && git checkout b893919ac673848622942d246657c4cf4e4fb856
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin de3996bd55e3394c60da31d346df70d0b7465e14 59c76e787e7b21dd493272fdd133042ed846a902 && git checkout -B drift-repro de3996bd55e3394c60da31d346df70d0b7465e14 && git merge --no-ff 59c76e787e7b21dd493272fdd133042ed846a902

node scripts/docs-audit/affected-docs.mjs --json de3996bd55e3394c60da31d346df70d0b7465e14

⚠️ 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 de3996bd55e3394c60da31d346df70d0b7465e14 → 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

domain:server#3 · marchtian · session_017ihL2dFb2QdZEHEJ83cR2B

维护者速读

  • 改了什么: 新增只读接口 GET /api/v1/sharing/shareable-objects,列出共享规则能生效的对象:业务对象在前、平台对象在后,各组按标签再按名称排序。Setup 的对象选择器以后只读这份清单。
  • 为什么改: Setup: show what people can really do and what really changed, using names rather than ids #30209 第 5 行选 B。以前选择器列出所有对象,选到不能共享的对象,要到保存时才被 SHARING_NOT_ENABLED 拒掉。现在清单用的就是保存时那道判定(sharingRuleObjectRefusal),把函数传进去,没有另写一份。
  • 风险与回滚: 纯新增的只读门,只有能管理共享规则的人能调用,与该面其他动词同一道准入。其他人得到 401 或 403,规则服务没运行时得到 501。凭据和会话类对象不在清单里。没有迁移,回滚即 revert。
  • 先看: shareable-objects.ts 的 listShareableObjects,以及路由文件里的拒绝顺序。
  • 两分钟验证: 见 PR 正文同名行。
  • 需要你做的: 无,席位落地。不是受管面,也不涉及 spec/skills/ADR,按新规不需要 Fable 复核。
  • 席位意见: ACCEPT。卡保持打开(Refs),第 1 行和控制台选择器由后续卡承接。

席位核对

ACCEPT · Head-sha: 59c76e787e7b21dd493272fdd133042ed846a902 · 2026-11-07T18:01Z

  1. Shape: draft → main, Refs #30209 (the card stays open for row 1 and the console picker). 13 files: plugin-sharing src and tests, four one-line gate wirings (dogfood parity import, check-route-envelope.mjs MODULES, cross-package-test-inputs.mjs, turbo.json), and the changeset. check-governed-merges --pr 31207: NOT governed, +950 / −4. Metadata-protocol: none; no packages/spec edit.
  2. Declared deviations, accepted:
    • Clause-②: yes (widening) where the claim said no. The diff adds a public HTTP read, and feat(service-messaging): serve the notification topics and channels a preference or template can name #31004 declared the same for its sibling door.
    • No widget hint on sys_sharing_rule.object_name yet. The field already has a working object-ref picker, and an unregistered hint degrades to a text box. The ui card that registers the widget flips the hint (open question, option A, seat's ruling).
    • isPlatformObjectSchema is extracted from effectiveSharingModel's default with no behaviour change, so the list's system flag reads the same spelling.
    • Attribution line 2 links the card thread (cosmetic).
    • The report's security_findings: withheld was a wrong value. The dev confirmed to the seat that it is none: no finding exists. The credential and session exclusions are this card's own acceptance item, not a finding.
  3. Gates (check-runs at this head): 64 runs, 56 success, 8 skipped, 0 failure, 0 pending. Dev's local reading: derived gates 89/89 exit 0 with --ran reconciled; plugin-sharing 1464 tests passed; five ablations went red and then green again (verdict filter, admission, anonymous branch, query refusal, ordering).
  4. Accepted, read in the diff:
    • Listing reads the save doors' own verdict, passed in as a function. An object on the list is one defineRule and the /data door admit; one left off is one they refuse.
    • Admission is assertCanManageRules through SharingRuleService.listShareableObjects, the same check every verb on this surface uses.
    • Refusal order matches feat(service-messaging): serve the notification topics and channels a preference or template can name #31004: caller resolution relayed (503/403), held sign-in 403, anonymous 401, any query parameter 400 VALIDATION_ERROR, non-admin 403 PERMISSION_DENIED, 501 with no rule service, 500 when the engine cannot enumerate.
    • The route is in the ledger, and its conformance test and the dogfood live-mount parity test both read it.
  5. Out of scope, to be filed by the seat: row 1 (a server card for a per-user effective-access read with origins, and a ui card for the widget on the user page that also carries row 5's widget hint). Row 2 was already done on main by fix(ui): Setup record pages show names, not ids, for sharing-rule recipients, conditions and sign-in audit rows #30496.
  6. Landing: seat lands it (pr_ready + automerge_enable via the relay). On MERGED it posts the landing record. pm:dispatched stays while Setup: show what people can really do and what really changed, using names rather than ids #30209 remains open for its follow-ups.

@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 42399338579 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (7/8) — 失败步骤: Run this shard's tests

    @objectstack/plugin-auth:test:  FAIL  src/organization-access-policy.test.ts > a switch of the active organization is judged against the target's limits > the absolute limit is judged too, and a targe
      ↳ 失败原因: @objectstack/plugin-auth:test: AssertionError: expected 'ended' to be 'admitted' // Object.is equality
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 10 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · marchtian · session_017ihL2dFb2QdZEHEJ83cR2B

Queue red triaged · 2026-11-07T18:27Z

Queue build 42399338579 failed in Test Core (7/8): packages/plugins/plugin-auth/src/organization-access-policy.test.ts:422, "the absolute limit is judged too, and a target with no limits admits the switch", expected 'ended' to be 'admitted'. The fixture stamps the session exactly 60 minutes old against a one-hour limit, and the check is now - created > limit, so any millisecond between the fixture's Date.now() and the judge's turns the CONTROL into ended. That is a timing precondition of the test, not product behaviour. It is already carded as #31175, with the fix in PR #31213 (moves the fixture off the boundary). This PR touches only plugin-sharing and four gate wirings; nothing in it reaches plugin-auth. Re-queued once at the same head 59c76e787e. If it goes red a second time on this test, the seat waits for #31213 rather than requeueing again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants