Skip to content

feat(spec): declare declaredRuleNamed on ISharingRuleService; document sharing-rule regime C - #25250

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

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-7afd2e

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

Fixes #25187

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

This PR handles the three regime C follow-ups the card lists: the contract member, the docs paragraph and the ledger docblock. It changes no behaviour.

What changed

  1. Contract (packages/spec/src/contracts/sharing-service.ts). ISharingRuleService gains an optional declaredRuleNamed?(name: string) member. It resolves to { name: string; packageId?: string } or null. It is optional, has the shape the activation door already duck-typed, and its docblock covers the probe-and-refuse duty and the 503.
  2. The door reads the contract's type (packages/runtime/src/domains/catalog-activation.ts). The local interface DeclaredSharingRuleReader is now a type alias: Required applied to a Pick of ISharingRuleService's declaredRuleNamed. The change is type-only: the typeof … === 'function' probe, the 404 and the 503 SERVICE_UNAVAILABLE stay as they were. A runtime build shows the symbol in no emitted file (see Evidence).
  3. SharingRuleService satisfies the member as declared. It already implements ISharingRuleService, and its declaredRuleNamed(name), which resolves to a DeclaredSharingRule or null, type-checks against the new member. Nothing in plugin-sharing changed.
  4. Docs (content/docs/permissions/sharing-rules.mdx). This adds a new section, "Declared rules are read-only: clone one, then switch it off". It covers read-only declared rules, clone and switch off through POST {basePath}/security/_activation/sharing_rule/:name, the 409 RESOURCE_CONFLICT refusal of a new or renamed row under a declared name, the manage_metadata gate plus the platform-operator wall, and the reconcile-failure window. Each claim cites the function and file it was read from at this head. The /meta half links to concepts/metadata-lifecycle, which this PR leaves unchanged and which stays consistent with the new section.
  5. Ledger docblock (packages/platform-objects/src/system/sys-metadata-activation.object.ts). The metadata_type description now lists every type the ledger holds at this head: flow (service-automation's flow store), action (objectql's action store), and position, permission, sharing_rule and webhook (the catalog activation door).
  6. Changeset .changeset/25187-declared-rule-reader-contract.md: @objectstack/spec and @objectstack/platform-objects at patch, following Metadata-protocol: none. @objectstack/runtime is not listed because its change is type-only and nothing in its published dist moves (see Evidence).

How the docs claims were checked against the code

  • Read-only. SharingRuleService.deleteRule refuses a declared name with PERMISSION_DENIED, and sharing-routes.ts handleError answers 403. PUT /meta/sharing_rule/:name is refused because the sharing_rule registry row in metadata-plugin.zod.ts sets allowRuntimeCreate: false.
  • 409. DeclaredSharingRuleNameConflictError sets code = 'RESOURCE_CONFLICT' and status = 409. It is thrown by defineRule (create) and by bindDeclaredRuleNameGuard's beforeInsert/beforeUpdate hooks (insert, and a rename onto a declared name). System writes are exempt.
  • Operator wall. In activation-gate.ts, refuseUngrantedActivationAuthoring requires manage_metadata. refuseUngrantedActivationWrite is inert when no posture is set, or under single; under a wall it requires posture === 'PLATFORM_ADMIN'. SHARING_RULE_ACTIVATION_SUBJECT.remedy names the platform operator.
  • Reconcile window. bindSharingRuleActivationReconcile registers a plain registerHook handler with no meta, so ObjectQL.opensAfterAbortUnit opens no unit. ObjectStoreMetadataActivationStore.setActive opens no transaction. So the ledger row stays written when the reconcile throws, and the door answers the error through errorFromThrown(err, 503). At boot, kernel:bootstrapped in sharing-plugin.ts runs the listRules backfill, which includes served declared projections, and then sweepOrphanedRuleGrants, which revokes the grants of a declared rule that is no longer served. A repeated switch re-runs the reconcile because setActive always issues an update, and afterUpdate fires on it.

Evidence

All readings were taken on head 672daf099, the last commit, unless noted otherwise.

  • pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated: VERDICT command-exit 0, "All 14 generated artifacts are up to date". No regeneration was needed.

  • pnpm --filter @objectstack/runtime --filter @objectstack/plugin-sharing --filter @objectstack/platform-objects --filter @objectstack/spec run typecheck: VERDICT command-exit 0, all four packages done.

  • Reverse verification (cross-package type change). The member's return was mutated to { name: number; … } through scripts/ablation-replace.mjs, and spec was rebuilt. tsc --noEmit then went red in both consumers:

    • runtime src/domains/catalog-activation.ts(159,36): error TS2322
    • plugin-sharing src/sharing-rule-service.ts(1050,9): error TS2416 Property 'declaredRuleNamed' in type 'SharingRuleService' is not assignable to the same property in base type 'ISharingRuleService'

    The restore was checked three ways: the blob equals HEAD with an empty git diff HEAD; after a spec rebuild, 0 dist files carry the mutated type; and both consumers' tsc passed again.

  • Tests:

    • vitest run src/domains/catalog-activation-door.test.ts (runtime): 64/64.
    • vitest run src/sharing-rule.test.ts (plugin-sharing): 140/140.
    • pnpm --filter @objectstack/platform-objects test: 87 files, 1278/1278.
  • Runtime publish measure. After pnpm --filter @objectstack/runtime build, a grep of dist/ (maps excluded) for DeclaredSharingRuleReader, ISharingRuleService and the new comment text finds 0 files each. The positive control declaredSharingRuleReaderOf is found in 2 files.

  • Gates. The local subset comes from node scripts/pm/dispatch-gates.mjs --commands (106 commands). Results are under "Local gates" below.

Local gates

All gates were run on head 672daf099.

  • Derived set. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 106 commands, and all 106 were run. The first pass ended with 103 at exit 0 and 3 at exit 3 (PREREQUISITE NOT MET, no build output): spec check:skill-examples, check:dual-build-cjs-loads and check:i18n. Their prerequisite builds were run (turbo, --concurrency=2), and all three then exited 0. check:i18n reported "all bundles in sync". check:skill-examples reported "262 prose examples type-check".
  • Reconciliation. dispatch-gates --ran reports "106 derived, 106 run, 0 NOT-MEASURED, 0 UNRUN", exit 0.
  • Local-only gates. pnpm check:dev-prereqs exited 0, and so did pnpm check:skill-frame-freshness.
  • Left to CI. The repo-wide farm (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Build Docs) is not repeated locally.

Acceptance notes

  • docs/adr/0126-packaged-metadata-customization-model.md:205 still lists metadata_type as ('flow', 'permission', …). It is a governed surface and outside this card, so it is left as is. The docblock this PR changes is the field's runtime description.
  • Not covered here, and recorded only as an observation: the /data door's 409 for the name guard is asserted by the error class's declared code/status and by the plugin-sharing tests. This PR adds no end-to-end HTTP pin for it.
  • Deviation from the claim's file surface. The claim listed three files plus a changeset. The dispatch asked for the runtime door to read the contract type in the same PR when it can, so packages/runtime/src/domains/catalog-activation.ts is touched too. The change is type-only.

Generated by Claude Code

…t regime C

Declare the declared-sharing-rule reader the catalog activation door uses as
an optional member of the sharing-rule contract, and have the door read the
contract's type instead of a local duck type (no behaviour change: the probe
and its 503 stay). Document regime C for declared sharing rules in
sharing-rules.mdx, and name every ledger type in the
sys_metadata_activation metadata_type description.

Claude-Session: https://claude.ai/code/session_01B2EeooonSXrANpkpgUSfFZ
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Oct 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/platform-objects, @objectstack/runtime, @objectstack/spec, touching 7 documentable anchor(s).

4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx (via sharing_rule (literal, a string literal in SysMetadataActivation; a string literal in a comment in ISharingRuleService))
  • content/docs/deployment/validating-metadata.mdx (via sharing_rule (literal, a string literal in SysMetadataActivation; a string literal in a comment in ISharingRuleService))
  • content/docs/kernel/runtime-services/sharing-service.mdx (via ISharingRuleService (symbol, a top-level interface))
  • content/docs/permissions/sharing-rules.mdx _(via declaredRuleNamed (symbol, a method of interface DeclaredSharingRuleReader; a method of interface ISharingRuleService), declaredRuleNamed (literal, a string literal in DeclaredSharingRuleReader), sharing_rule (literal, a string literal in SysMetadataActivation; a string literal in a comment in ISharingRuleService), /security/activation/sharing_rule/:name (route, a path literal in a comment in ISharingRuleService))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v18/18-0.mdx (via sharing_rule (literal, a string literal in SysMetadataActivation; a string literal in a comment in ISharingRuleService))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 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 — 149 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 80ec71dc26036d43bac03186224fc673864da517 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 80ec71dc26036d43bac03186224fc673864da517

⚠️ 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 80ec71dc26036d43bac03186224fc673864da517 → 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

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 672daf099fdc51d6fb44c2bcb3c9b907f4370329
Local-runs: none
Scope: packages/spec/src/contracts/sharing-service.ts + packages/runtime/src/domains/catalog-activation.ts, content/docs/permissions/sharing-rules.mdx, packages/platform-objects/src/system/sys-metadata-activation.object.ts, .changeset/25187-declared-rule-reader-contract.md, packages/plugins/plugin-sharing/src/sharing-rule-service.ts, packages/plugins/plugin-sharing/src/sharing-plugin.ts, packages/plugins/plugin-sharing/src/declared-sharing-rules.ts, packages/runtime/src/domains/activation-gate.ts, packages/core/src/utils/metadata-activation-store.ts, packages/objectql/src/engine.ts, packages/rest/src/sharing-routes.ts, packages/spec/src/kernel/metadata-plugin.zod.ts, packages/spec/src/security/tenancy-posture.ts, packages/services/service-automation/src/flow-activation-store.ts, packages/objectql/src/action-activation.ts, content/docs/concepts/metadata-lifecycle.mdx, docs/adr/0131-total-organization-ownership-no-null-organization-id.md, docs/adr/0126-packaged-metadata-customization-model.md

Reviewed 2026-10-22T16:17Z on card #25187 (body and all four comments, the os-dev-report 6344788817 included), PR #25250 (body, file list, net diff against main at f2ade4afb), and the 39 check-runs on the head. Every path of the diff is inside this scope, so there is no TIER_DEFAULT remainder for the lane seat. The head did not move during the review.

Check-runs on the head (the gate verdicts). All 39 completed: 0 failure, the rest success or skipped (opt-in and matrix-skipped lanes). Green by name: Check Changeset, Lint (ESLint, docs & code guards, repo & tooling gates, CI/release/engine gates, PM dispatch-gates, scoped lanes), Type Check (workspace, source gates, consumer gates, debt ledger), Test Core 1–6, Build Core, Build Docs, Check Documentation Links, Flag docs affected by code changes, Spec property liveness, Governed Surface Queue Guard, the card/branch claim guards, Dogfood Regression Gate. At the first read 15 of these were still in progress; by this write all had concluded.

① Derived judgments

  1. ISharingRuleService.declaredRuleNamed?(name) — a widening, right. An optional method on a published interface: every existing implementer still satisfies the contract without change, and a caller must narrow before calling. The one in-tree implementer, SharingRuleService (packages/plugins/plugin-sharing/src/sharing-rule-service.ts:1050), returns Promise<DeclaredSharingRule | null>; DeclaredSharingRule (declared-sharing-rules.ts:144) carries name: string and packageId?: string, so it is structurally assignable to the declared { name: string; packageId?: string } | null. The dev's reverse verification (return mutated to name: number, tsc red at catalog-activation.ts(159,36) and sharing-rule-service.ts(1050,9) TS2416) is consistent with that reading: the contract now binds both consumers. The docblock's claims (answers for the declaration in every organization, ledger-independent; the door records packageId; optional, caller probes and refuses fail-closed with 503) each match the code read below. Right.
  2. The runtime door's switch from its local duck type to the contract type keeps the 503-when-absent behaviour — right. type DeclaredSharingRuleReader = Required<Pick<ISharingRuleService, 'declaredRuleNamed'>> (catalog-activation.ts:120) is the same shape the deleted local interface spelled. declaredSharingRuleReaderOf still probes typeof candidate?.declaredRuleNamed === 'function' (line 124); DECLARATION_LOOKUPS.sharing_rule.lookup still returns undefined when the probe fails (line 159); the !pending arm still answers 503 SERVICE_UNAVAILABLE, a rejected read 503, a null answer 404 (lines 287–325). No control flow moved. The alias is module-local and the import is import type, so nothing new reaches runtime's public surface; the dev's dist grep (0 files for the alias, the contract name and the new comment; the positive control declaredSharingRuleReaderOf in 2 files) is consistent with that. Right.
  3. The ledger's metadata_type description — right, and complete at this head. Every writer of sys_metadata_activation on this head: flow (service-automation/src/flow-activation-store.ts, extends ObjectStoreMetadataActivationStore), action (objectql/src/action-activation.ts, same), and position / permission / sharing_rule / webhook (catalog-activation.ts:396, CATALOG_ACTIVATION_SUBJECTS). The other metadata_type references in core, plugin-auth and plugin-sharing are readers of position / permission. The six names listed are the six written. A description on a system object's field is not something a metadata author writes, so it moves no authorable surface. Right.
  4. The docs paragraph in sharing-rules.mdx, claim by claim against the cited code — every claim holds.
    • Read-only: DELETE …/sharing/rules/:idOrName on a declared name answers 403 PERMISSION_DENIED, switched off or not. SharingRuleService.deleteRule (line 1158): no row found, then declaredRuleNamed(idOrName) truthy, then it throws an Error whose message starts PERMISSION_DENIED:; declaredRuleNamed reads the declared source without consulting the ledger, so a switched-off rule still answers. The REST prefix arm (packages/rest/src/sharing-routes.ts:578, and the ['PERMISSION_DENIED', 403] map at line 386) renders it as 403 with code: 'PERMISSION_DENIED'. Right. (The PR body cites this file as sharing-routes.ts handleError; it lives in packages/rest, not in plugin-sharing. The mdx itself cites only the service, so nothing in the published text is wrong.)
    • PUT …/meta/sharing_rule/:name is refused too. sharing_rule is a registry type in metadata-plugin.zod.ts:190 under the allowRuntimeCreate: false ruling; content/docs/concepts/metadata-lifecycle.mdx:121 (unchanged, linked from the new section) spells the answer, 403 NOT_CREATABLE / NOT_OVERRIDABLE. Consistent.
    • The door writes one sys_metadata_activation row (metadata_type: 'sharing_rule', no organization column), the same request revokes or re-derives the grants. ObjectStoreMetadataActivationStore.setActive (core/src/utils/metadata-activation-store.ts:260) writes exactly one row keyed by (metadata_type, name); bindSharingRuleActivationReconcile (sharing-rule-service.ts:325) registers afterInsert / afterUpdate / afterDelete on METADATA_ACTIVATION_TABLE and calls reconcileDeclaredRuleActivation(name), which purges { source: 'rule', source_id: name } when the rule is served nowhere and runs evaluateResolvedRule per projection when it is (lines 1068–1078). Right.
    • The door confirms the name through declaredRuleNamed; unknown name 404; no reader 503. Item 2 above. Right.
    • A new row under a declared name is refused 409 RESOURCE_CONFLICT, so is a rename onto one; applies to POST …/sharing/rules, Setup's forms, scripts and imports through the data API; an update that keeps its name is not affected. DeclaredSharingRuleNameConflictError declares code = 'RESOURCE_CONFLICT', status = 409 (lines 251–253); defineRule's create branch throws it (line 834); bindDeclaredRuleNameGuard registers beforeInsert and beforeUpdate on sys_sharing_rule (lines 308–309), and the update arm returns early when data.name === previous.name (line 301). The message names the clone path. System writes pass (line 293), which the docs do not contradict. Right.
    • Who may flip: manage_metadata (403), then under group / isolated the platform operator, the message naming the operator; under single the manage_metadata holder is enough. refuseUngrantedActivationAuthoring requires ACTIVATION_AUTHORING_CAPABILITY = 'manage_metadata' and answers 403 PERMISSION_DENIED; refuseUngrantedActivationWrite returns undefined when no posture or !postureEnforcesWall(posture) (tenancy-posture.ts:53: wall = posture !== 'single'), else requires ec.posture === 'PLATFORM_ADMIN', else 403 PERMISSION_DENIED with SHARING_RULE_ACTIVATION_SUBJECT.remedy, which reads "ask your platform operator". Right.
    • The reconcile-failure window: the hook runs after the row is written, no transaction wraps the two; a throw reaches the door, which answers the error instead of 200; the row keeps its new value; boot heals; the same switch again re-runs the reconcile. ObjectQL.opensAfterAbortUnit (engine.ts:5522) opens a unit only for a hook entry with meta (a metadata-bound hook); the reconcile handler is registered through plain registerHook with object / packageId / priority and no meta, so no unit is opened; setActive opens no transaction of its own; triggerHooks (engine.ts:5443) awaits each handler with no catch, so the throw leaves engine.update, and the door's catch answers deps.errorFromThrown(err, 503) (catalog-activation.ts:409). At boot, kernel:bootstrapped (sharing-plugin.ts:1074) runs backfillRuleGrants over listRules({}), then sweepOrphanedRuleGrants; declaredRules filters out readSwitchedOffDeclaredRules() (line 943), so a switched-off declaration has no live projection and its name-keyed grants are revoked by the sweep (lines 1272–1335), and a switched-on one is re-derived by the backfill. A repeat switch always issues an update when the row exists (setActive, line 270), and afterUpdate fires on it. Right. One edge the docs leave out and need not state: under unknown declared scopes (declaredScopes === null) the sweep holds every declared name live and revokes nothing (line 1296); that is the service's deliberate fail-safe, not a counter-claim.
    • The section's placement (inside "Criteria-Based Sharing Rules", after "Switching a rule off withdraws the access it granted", before "There is no share every record rule") and its {basePath} spelling match the page's existing conventions. ADR-0131 D6's sharing_rule note (read-only, clonable, same-name shadowing retired, deployment-level and operator-gated under a wall) is what the section paraphrases.
  5. No accept-set change anywhere else. plugin-sharing is untouched; no Zod schema, registry row, route or error code moves; no generated artifact in the tree carries the changed strings (git grep at the head: declaredRuleNamed appears only in the five diff files and the door's test; the old description string survives only in ADR-0126:205, which is ③ item 1). Spec's check:generated ("All 14 generated artifacts are up to date") and the green Spec property liveness and Lint lanes agree.

② Semver level

  • Clause-②: yes (widening) on the PR body and in the changeset body, both line-initial — true: one optional member added to a published contract, every implementer still compiles, every caller still compiles.
  • Metadata-protocol: none — true: nothing a metadata author can write changes (a contract method and a system field's description are not authorable keys, values or types).
  • @objectstack/spec and @objectstack/platform-objects at patch — right for a non-protocol widening (AGENTS.md § Post-Task Checklist step 3: none takes patch). Not skip-changeset, correctly: two released packages publish.
  • @objectstack/runtime not listed — acceptable: the runtime change is a module-local type alias plus an import type, and emits nothing to dist (the dev's measure, consistent with the source read). Independently, .changeset/config.json places @objectstack/runtime in the same fixed group as @objectstack/spec, so it versions in lockstep with the two listed packages whatever the changeset names. Check Changeset is green on the head.
  • No major, no BREAKING banner, no ADR-0087 disposition owed: nothing is removed, renamed or tightened.

③ Boundary flags

  • open_questions: none declared by the dev; none found here.
  • Deviation — branch claude/project-thread-7afd2e instead of claude/issue-25187-*. The newest Claim on the card (6343530887) names this branch and the seat's platform exception; the gate "The card this PR closes must claim this branch" is green. Answered: no action.
  • Deviation — packages/runtime/src/domains/catalog-activation.ts touched beyond the claim's three files. Type-only, stated in the PR body, and the dispatch asked for the door to read the contract type in the same PR. It is inside this record's scope (item ① 2) and judged there. Answered: no action.
  • Deviation — PR body opens with the attribution HTML comment, Fixes #25187 on line 4. A dispatch instruction the dev followed; the claim guards are green, and it is not a contract matter. Answered: no action.
  • Deviation — origin/main not merged, 5 commits behind. None of the five touches these packages or files; CI judged the merge ref and every lane is green. Answered: no action.
  • Out-of-scope finding 1 — ADR-0126:205 still lists metadata_type as ('flow', 'permission', …). Confirmed on the head. The table row is illustrative (ellipsis) rather than wrong, and ADR-0126 is a governed surface (Tier H), so it cannot ride this PR. Answer: card — a p4 docs-only card for a maintainer-worded ADR touch-up, dedupe words "ADR-0126 metadata_type column list sharing_rule webhook". Not a maintainer escalation: nothing is blocked by it.
  • Out-of-scope finding 2 — no end-to-end HTTP pin for the /data door answering 409 RESOURCE_CONFLICT from bindDeclaredRuleNameGuard. The envelope is pinned at the class (code / status) and in sharing-rule.test.ts; the transport reads thrown status / code through its shared rule. A socket-level pin is a test-only addition the domain:spec-adjacent lane can take when it next touches the door. Answer: card — a p4 test-only card to the plugin-sharing/runtime lane; not a defect, not a maintainer matter.

Implemented-by: claude/project-thread-7afd2e
Reviewed-by: session_01B2EeooonSXrANpkpgUSfFZ

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:spec seat 2 (#18549) · os-bill · session session_01B2EeooonSXrANpkpgUSfFZ · 2026-10-22T16:20Z · head 672daf099fdc51d6fb44c2bcb3c9b907f4370329

  1. Shape: draft, base main, one closing keyword (Fixes #25187, line 4, after the session's attribution block). check-governed-merges.mjs --pr 25250 reads not governed and +119/−4, so this is an ordinary queue landing. mergeable_state: clean.
  2. Premise measured: the dev read git grep declaredRuleNamed -- packages/spec/src → 0 at f2ade4afb, and the runtime door duck-typed the same shape that SharingRuleService implements. Both held.
  3. Gates (check-runs on this head): 36 success and 6 path- or opt-in-skipped, with none failed or pending. Locally: 106 dispatch-gates families, 106 run (--ran: 0 NOT-MEASURED, 0 UNRUN). The reverse verification of the cross-package type went red in both consumers and green again on restore.
  4. Contract review: PASS at CONTRACT_REVIEW_TIER on this head, record 6345609374.
  5. Accepted deviations:
    • The branch is this seat session's designated claude/project-thread-7afd2e, as claim 6343530887 names it.
    • catalog-activation.ts was touched, type-only, at the order's request.
    • origin/main was not merged before opening the PR (5 commits behind, none in these files).
    • The session's attribution block takes PR body lines 1–2.
  6. Prose faces (the seat's own read):
    • the contract member's docblock;
    • the regime C section of sharing-rules.mdx, whose every claim the review verified against cited code;
    • the ledger field description (six values, exactly those written on this head);
    • the changeset (patch for spec and platform-objects, Clause-②: yes (widening), Metadata-protocol: none).
  7. Out of scope:
  8. Landing: pr_ready + automerge_enable through the relay now. The seat follows it to MERGED.

Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

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

    @objectstack/metadata-protocol:test:  FAIL  src/reference-sites.derivation.test.ts > [#9190] derived reference sites — derivation is a pure function of the schemas > two derivations of the same schema
      ↳ 失败原因: @objectstack/metadata-protocol:test: Error: Test timed out in 5000ms.
    

↳ 失败原因 是判读的关键:超时(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,按失败测试文件聚合):

  • src/reference-sites.derivation.test.ts — 24h 窗口内只有本 PR 撞到过,暂不汇总(再有一个不同 PR 撞到就会自动开汇总 issue)。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

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

分诊清单:

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

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

Merged via the queue into main with commit dfab597 Oct 22, 2026
43 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-7afd2e branch October 22, 2026 17:00
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/m tooling

Projects

None yet

2 participants