Skip to content

docs(spec): Seed.locale says which loads filter on it, and the ownership-queue contract says the registered service honours onFailedRead refuse - #30604

Merged
objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/issue-30347-spec-docblock-fold
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/issue-30347-spec-docblock-fold

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

domain:spec#3 · os-sales · session_01L6bhgTqqeHSwZYJmApbDCa

Requested by Sales · project thread

Fixes #30347
Fixes #30389

Clause-②: no
Metadata-protocol: none

维护者速读

What changes

Wording only, in one package (@objectstack/spec, patch). Each new sentence below names the line on origin/main (ffeda137b, merged into this branch in round 2) it rests on.

#30347: SeedSchema.locale (packages/spec/src/data/seed.zod.ts)

Before (docblock): "Publishing and installing are locale-neutral acts — this axis is scoped to the boot path, by decision. … SeedLoaderConfig.locale, which only the boot path supplies … Package apply, draft publish and marketplace install each build their own load request and pass no locale …"

Before (.describe()): "The publish and install paths do not filter by locale — they load every dataset and warn"

After: the docblock paragraph becomes three bullets, and the .describe() says the same in one sentence. Round 1 (contract review should-fix 1 and 2) names the reseeding user in the .describe() and says what rung 1 reads for a load that names no organization.

New sentence Rests on
A request with no locale loads a locale-scoped dataset for every locale, and the loader warns packages/runtime/src/seed-locale.ts:30-32 (no rung ⇒ undefined, warnOnUnresolvedLocaleScope); marketplace-install-local-plugin.ts:5305-5307 (spread, never locale: undefined)
The boot seed and the dev hot-reload seeder filter on the app's i18n.defaultLocale packages/runtime/src/app-plugin.ts:1937 (boot) and :2367 (hot reload), both this.resolveSeedLocale(), which reads i18n.defaultLocale at :2299-2303
Install, reseed, heal and per-organization replay filter on what resolveSeedLocaleForLoad resolves its four callers: packages/cloud-connection/src/marketplace-install-local-plugin.ts:5239-5242 (applySideEffects, reached by install :2537 and reseed :4376), :1465 (restart heal), :1728 (that plugin's own replayer); packages/runtime/src/app-plugin.ts:1825-1830 (the AppPlugin per-organization replayer). All four go through runInlineSeed (:5287-5292) or call the producer directly
Rung 1: the localization.locale setting for the organization, only when source is not 'default' seed-locale.ts:135-140
Rung 1, a load that names no organization (a single-tenant install or reseed, the restart heal) still reads the setting: the environment override answers first, then the global setting, then, where no wall stands and the deployment holds exactly one organization, that organization's own setting seed-locale.ts:137 (settings.get('localization', 'locale', {}) when no organization is named); packages/services/service-settings/src/settings-service.ts:1951-1966 (the env override answers before any row is read), :2119 and :2175 (the cascade order global, tenant, default, first non-null wins), :2137-2149 (the tenant rung, read for a tenant-scoped key: localization is scope: 'tenant', manifests/localization.manifest.ts:39); settings-service.ts:2221-2227 (organizationReachOf hands an organization-less caller to resolveOrganizationReach), packages/core/src/security/organization-reach.ts:87-95 (no wall ⇒ the derived organization becomes the reach), packages/core/src/security/resolve-authz-context.ts:1676-1696 (resolveGuestOrganization) and packages/core/src/security/system-write-organization.ts:115-124 (single posture and exactly one sys_organization ⇒ derived); the heal passes no organization (marketplace-install-local-plugin.ts:1465)
Rung 2: sys_user.locale of the user who installs, reseeds or founds, when one started the load (the .describe() now says "the installing, reseeding or founding user") seed-locale.ts:142-153; install passes admission.userId (marketplace-install-local-plugin.ts:1744 to :2537 to :5239-5241), reseed passes the reseeding user's admission.userId (:4376 to :5239-5241), the heal passes no user (:1462-1465)
Rung 3: the app's i18n.defaultLocale, when the caller names one seed-locale.ts:128-129, 155; the marketplace plugin's own replayer passes none (marketplace-install-local-plugin.ts:1723-1728)
A setting or user read that fails seeds nothing rather than another language seed-locale.ts:34-39 (the throw reaches the caller, which writes nothing)
Package apply and draft publish pass no locale packages/runtime/src/domains/packages.ts:4000-4007 and packages/metadata-protocol/src/protocol-publish-batch.ts:115-125: neither request carries locale
The #17011 bound is narrowed to those two paths by the maintainer's ruling (2026-11-05) seed-locale.ts:7-9 ("maintainer ruling A, 2026-11-05 … reversing the install-path bound recorded on SeedSchema.locale")

The .describe() change regenerates four references pages, all committed: content/docs/references/data/seed.mdx, data/seed-loader.mdx, api/package-api.mdx, kernel/manifest.mdx.

#30389: packages/spec/src/contracts/ownership-queue-service.ts

  1. Before: "the registered one, @objectstack/plugin-sharing's, honours 'refuse' from plugins: a queue sharing recipient still revokes grants on a member-table outage, because the queue expander answers "nobody" for every consumer; refuse for the sharing-grant pass and the approver slate (ADR-0142 amendment first) #29604's code half on, so until then a caller passing 'refuse' still gets 'nobody'." After: "the registered one, @objectstack/plugin-sharing's, honours 'refuse'." Rests on packages/plugins/plugin-sharing/src/ownership-queue-service.ts:270-279 (expandMembers) and :290-297 (isMember), which take onFailedRead and wrap a failure in queueReadRefusal (:168).
  2. Before: "whether that posture moves is plugins: a queue sharing recipient still revokes grants on a member-table outage, because the queue expander answers "nobody" for every consumer; refuse for the sharing-grant pass and the approver slate (ADR-0142 amendment first) #29604's code half to decide." After: "That posture stays by decision: it is the one the sharing-rule service takes for the same ledger on its own position recipients, so a queue's position and a rule's position cannot disagree during an outage." Rests on ownership-queue-service.ts:670-688 (switchedOff catches the ledger read and returns nothing switched off, under either answer) and packages/plugins/plugin-sharing/src/sharing-rule-service.ts, readPositionActive (a failed ledger read answers active).
  3. Added to isMember's doc: "Under 'refuse' the registered service reads the queue's own rows (sys_queue, sys_queue_member) first, and a failure there rejects at once, since without them the queue's member set would be incomplete (a declared queue's own specifications come from the registry, but its authored additions come from sys_queue_member). Past them it answers true as soon as a read that succeeded reaches userId, even beside a failed read, and rejects only when no successful read reaches userId and one failed." Rests on ownership-queue-service.ts:232-261 (resolve reads authoredQueueRow and authoredMemberSpecs with refuse before any specification; a declared queue's specifications come from declaredMemberSpecs at :243, its authored additions from authoredMemberSpecs at :237) and :476-496 (specsReachUser: returns true on the first reaching spec, keeps the first failure, throws it only after the walk). Round 1 (contract review should-fix 3) replaced the earlier reason, "since without them the call does not know what to ask", which overstated it for a declared queue.

Left as it was, because it is true: the 'nobody' bullet still says the admission consumers (D3, D4, D5) take 'nobody'. Measured: sharing-service.ts:952, security-plugin-authz-middleware.ts:1610 and objectql/src/assignment-rules.ts:293 call without the option; only D6 (approval-service.ts:3541) and D7 (sharing-rule-service.ts:1752, :2198) pass 'refuse'.

Tests

No behaviour changed, so no new test. Round 2 readings were taken on head d0863ab93 (after merging origin/main ffeda137b).

  • pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/data/seed.test.ts src/data/seed-loader.test.ts src/data/seed-file.zod.test.ts src/data/define-seed-record-keys.test.ts: 4 files, 111 tests passed (the spec tests that touch seed.zod.ts; round 2 changes no other file's source).
  • pnpm --filter @objectstack/spec build, then gen:docs, then check:generated: "All 14 generated artifacts are up to date".
  • pnpm --filter @objectstack/spec typecheck: exit 0, 0 errors.
  • eslint on seed.zod.ts: exit 0.
  • node scripts/check-changeset-no-major.mjs --base origin/main --event (this body as a pull_request event): exit 0, "a non-protocol change at patch".
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 96 commands, run after a full turbo run build (119/119), all exit 0, exit codes recorded before any pipe. --ran: "96 derived, 96 run, 0 NOT-MEASURED, 0 UNRUN".

Acceptance notes


Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01L6bhgTqqeHSwZYJmApbDCa

…ed, heal and per-organization replay pick by the resolved locale

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6bhgTqqeHSwZYJmApbDCa
…ibe, and add the changeset

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6bhgTqqeHSwZYJmApbDCa
…honours onFailedRead 'refuse', the ledger posture stays, and when isMember answers true beside a failed read

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6bhgTqqeHSwZYJmApbDCa
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation protocol:data tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 2 documentable anchor(s).

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

  • content/docs/data-modeling/seed-data.mdx (via SeedSchema (symbol, a top-level const))
What this run could not see
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 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; 103 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 — 170 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 ffeda137b8412b11c909a6d00357f46e0cd555d2 → packageMentionDocs.

Which tree this was computed on

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

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

⚠️ 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 ffeda137b8412b11c909a6d00357f46e0cd555d2 → 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:spec#3 · os-sales · session_01L6bhgTqqeHSwZYJmApbDCa

Contract-review-claim: session_01L6bhgTqqeHSwZYJmApbDCa · head 9d61fa4 · 2026-11-05T17:48Z

Full review at CONTRACT_REVIEW_TIER (published schema face: data/seed.zod.ts, contracts/ownership-queue-service.ts; wording only), run by an isolated at-tier subagent of this seat.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-sales · session_01L6bhgTqqeHSwZYJmApbDCa

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 9d61fa4aa466b55536b5068919b659f2d94c2a84
Local-runs: none
Scope: packages/spec/src/data/seed.zod.ts, packages/spec/src/contracts/ownership-queue-service.ts + packages/runtime/src/seed-locale.ts, packages/runtime/src/app-plugin.ts, packages/cloud-connection/src/marketplace-install-local-plugin.ts, packages/plugins/organizations/src/organizations-plugin.ts, packages/runtime/src/domains/packages.ts, packages/metadata-protocol/src/protocol-publish-batch.ts, packages/metadata-protocol/src/seed-loader.ts, packages/plugins/plugin-sharing/src/ownership-queue-service.ts, packages/plugins/plugin-sharing/src/sharing-rule-service.ts, packages/plugins/plugin-sharing/src/sharing-service.ts, packages/plugins/plugin-security/src/security-plugin-authz-middleware.ts, packages/plugins/plugin-security/src/security-plugin-explain-access.ts, packages/objectql/src/assignment-rules.ts, packages/plugins/plugin-approvals/src/approval-service.ts, packages/spec/liveness/seed.json

Inputs: cards #30347 and #30389 (bodies, triage, claims, dev report 6665047586 and its pointer), PR #30604 body, file list and net diff against the merge base 2ffdb6497, and the check-runs on this head. Cited code read on origin/main 4831d5006 (every file above except approval-service.ts is byte-identical to the merge base; the 'refuse' call sits at :3496 there and :3541 on origin/main). Rest of the diff — four regenerated references pages and the changeset — is the lane seat's ACCEPT at TIER_DEFAULT; the ② line below reads the changeset only for the semver claim.

① Derived judgments

  • Accept-set: unchanged — right. SeedSchema.locale stays z.array(LocaleSchema).min(1).optional() inside the same strictObject; only the docblock and the .describe() string move. No key, value, default or refusal moves anywhere in the diff.
  • Public surface: unchanged — right. No export added, removed or renamed; OwnershipQueueFailedReadAnswer, IOwnershipQueueService.expandMembers and isMember keep their signatures; onFailedRead keeps its default 'nobody'. api-surface/ untouched, correctly.
  • Published wording moves — right, and the regeneration is complete. The .describe() ships in src/**/*.zod.ts and in the generated JSON-schema description, and renders on four references pages (data/seed.mdx, data/seed-loader.mdx, api/package-api.mdx, kernel/manifest.mdx), all four regenerated on this head to the new string; git grep "do not filter by locale" on the head hits only the "package apply and draft publish" spelling.
  • Dropping the "until then a caller passing 'refuse' still gets 'nobody'" clause is wording, not a behaviour change — right. The registered service already honours the key on main (plugin-sharing/src/ownership-queue-service.ts:264-298); the clause described a state that no longer exists.
  • No ADR moves — right. ADR-0142 D2's amended paragraph is what the contract already cites; the new ledger sentence records the posture the code takes, it does not decide a new one.

② Semver level

@objectstack/spec: patch — matches. Clause-②: no and Metadata-protocol: none are line-initial in both the changeset body and the PR body and match the diff: nothing an author can write is added, narrowed or re-meant; the only published change is TSDoc and .describe() text, a non-protocol change, so patch is the level and the Check Changeset gate agrees. Changeset title and body describe the two sentences accurately. packages/spec/CHANGELOG.md (release-owned, still carrying the "publish and install paths do not filter by locale" entry) is correctly left alone.

③ Boundary flags

Every dev flag answered; open_questions: [] and no security findings.

  1. Two commits for spec(seed): the Seed.locale docblock and .describe() say installing is locale-neutral, but the install paths now pick by the organization's locale #30347 (regenerated pages and changeset in 9ba263492 after the source commit 3bbc3eb9b) — accepted. Declining a --force-with-lease on a pushed branch is the safe reading of discipline §3; the queue squash-merges, so the split is invisible on main. No action.
  2. PM assumption "rung 3 is always the app's i18n.defaultLocale" measured narrower — confirmed against the code: the marketplace plugin's own replayer passes no app (marketplace-install-local-plugin.ts:1711-1716) and the restart heal passes no user (:1450-1453). The text's "when one started the load" / "when the caller names one" is the correct narrowing.
  3. PM assumption "spec(ownership-queue-service): the onFailedRead docblock still says the registered service does not honour 'refuse' yet #30389 sentence 2 is the D3/D4/D5 posture" measured narrower — confirmed: the sentence it replaced was about the activation-ledger read (switchedOff, ownership-queue-service.ts:670-690), whose failure still reads "switched on" under both answers. D3/D4/D5 taking 'nobody' was already stated in the next bullet and still holds (callers with no option: sharing-service.ts:952, security-plugin-authz-middleware.ts:1610, assignment-rules.ts:293; security-plugin-explain-access.ts:512 too).
  4. PM assumption "rejects only when no successful read does" measured narrower — confirmed: a failed sys_queue (authoredQueueRow, :576-580) or sys_queue_member (authoredMemberSpecs, :646) read under refuse throws inside resolve (:232-262) before any specification is read. The added isMember sentences say so.
  5. Worktree created under the shared checkout, then moved before any edit — process note only; the diff touches nothing outside the seven files. No action.
  6. Out-of-scope finding (liveness note) — escalated, not folded. packages/spec/liveness/seed.json:53, the locale row's note, still says runInlineSeed (marketplace install) passes no locale and that "publish and install are locale-neutral acts" — false on main since PR feat(runtime,cloud-connection,organizations): the marketplace install, sample-data reseed, restart heal and per-organization replay pick seed datasets by the organization's language #30346 and of the same class as spec(seed): the Seed.locale docblock and .describe() say installing is locale-neutral, but the install paths now pick by the organization's locale #30347. Correctly left out of this claim's file surface (a ruling record, not a mechanical edit). Owed its own domain:spec card; the producer field of the same row, which names only AppPlugin.resolveSeedLocale, belongs in it (the install paths' producer is now resolveSeedLocaleForLoad).
  7. Docs-drift advisory on content/docs/data-modeling/seed-data.mdx — read: the page already carries the narrowed bound (:339-342, from PR feat(runtime,cloud-connection,organizations): the marketplace install, sample-data reseed, restart heal and per-organization replay pick seed datasets by the organization's language #30346). Nothing owed.

④ Design judgment

Each new sentence judged against the code it cites on main; each tried against a path the text does not name.

packages/spec/src/data/seed.zod.ts — SeedSchema.locale docblock and .describe()

  • "The loader evaluates this axis against SeedLoaderConfig.locale; a load request that carries none loads a locale-scoped dataset for every locale, and the loader warns naming each dataset" — TRUE: seed-loader.ts warnOnUnresolvedLocaleScope (:3481-3497) and filterByLocale (:3551-3560, if (!locale) return datasets).
  • "The boot seed and the dev hot-reload seeder filter on the app's declared i18n.defaultLocale (AppPlugin)" — TRUE: app-plugin.ts:1937 and :2367, both this.resolveSeedLocale() (:2299-2303). Tried: a multi-tenant boot skips the inline seed (:1913-1919) and the replay covers it — not a contradiction.
  • "The marketplace install, the sample-data reseed, the restart heal and the per-organization replay filter on the locale resolved for that load by resolveSeedLocaleForLoad" — TRUE and complete. SeedLoaderRequestSchema has exactly six production builders on main (three in app-plugin.ts, runInlineSeed in the marketplace plugin, package apply, draft publish); runInlineSeed (:5141-5179) is reached by install (handleInstall → applySideEffects :2525 → :5108), reseed (:4347 → :5108), the restart heal (:1453) and the plugin's own replayer (:1716); the AppPlugin replayer resolves at :1825-1830. Tried: a reinstall/upgrade goes through the same install door — covered as "install"; the restart rehydrate calls applySideEffects with seedNow: false (:1354) — no load; SeedLoaderService.validate (dry-run) has no production caller.
  • Rung 1, "the localization.locale setting … counted only when somebody set it (source is 'default' is the platform's placeholder)" — TRUE on the mechanism: seed-locale.ts:135-140. Should-fix (precision): "for the organization the rows land in" under-describes a load that names no organization — a single-tenant install or reseed, and the restart heal, which only runs with the organization wall inactive (:1431): there the read is settings.get('localization', 'locale', {}) (:137) and the service's own reach (env override, the global row) still decides. The sentence reads as organization-only; a reader would conclude rung 1 is skipped in single-tenant, which it is not.
  • Rung 2, "the sys_user.locale of the user who installs, reseeds or founds the organization, when one started the load" — TRUE: seed-locale.ts:142-153; install passes admission.userId (:1732 → :2525), reseed passes admission.userId (:4347), the replays pass the founder (organizations-plugin.ts:416-420, from attributedUserId; absent on a system insert → no user, as the text says), the heal passes none. Docblock complete. Should-fix (completeness, the published .describe()): the .describe() spells this rung "the installing or founding user's locale" while naming the reseed among the paths in the same sentence — the reseed resolves on the reseeding user, who need not be the installer. One word ("installing, reseeding or founding") and a gen:docs regeneration of the four pages.
  • Rung 3, "the app's declared i18n.defaultLocale, when the caller names one" — TRUE: seed-locale.ts:127-130, 155; install/reseed pass the package body (:5111, read by appDefaultSeedLocale :111-120), the heal passes converted (:1453), the AppPlugin replayer passes the boot bundle's (:1795, :1829), the marketplace replayer passes none (:1716).
  • "No answer is a load with no locale, as above" — TRUE: every caller spreads the key (:1853, :5176), so absence, not undefined, reaches the loader and its warning fires.
  • "A setting or user read that fails seeds nothing rather than another language" — TRUE on every path: the throw leaves resolveSeedLocaleForLoad (:34-39, no catch at :137 or :145); install/reseed catch it as seed-error with no loader run (:5117-5120), the heal catches and records (:1488-1490), the per-organization replay's caller logs "falling back" and then falls back to nothing — the organization starts empty (organizations-plugin.ts:436-480; the donor-clone and orphan-claim fallbacks were removed). Tried: a load whose datasets declare no locale never reads rungs 1-2 (:130), so a failing settings service cannot block it — consistent with the sentence (no read, no failure).
  • "Package apply and draft publish pass no locale … the bound ruled on Seed locale scope is silently inert on the three publish/install-time seed-load paths — package apply, draft publish and marketplace install pass no config.locale #17011, narrowed to these two paths when the maintainer ruled that the install paths pick by locale (2026-11-05)" — TRUE: domains/packages.ts:3985-3992 and protocol-publish-batch.ts:115-126 build config with multiPass only; the ruling is recorded at seed-locale.ts:5-9.
  • Sentences left in place: the matching rule, "omitted means every locale", the min(1) rationale — all still true against datasetAllowsLocale (:349).

packages/spec/src/contracts/ownership-queue-service.ts — OwnershipQueueFailedReadAnswer and isMember

  • "the registered one, @objectstack/plugin-sharing's, honours 'refuse'" — TRUE: expandMembers :264-280 and isMember :282-298 take the key through newPass (:203-208); under refuse every member-table read throws (expandSpec :421-424, holderCheck :529-539, authoredQueueRow :576-580, authoredMemberSpecs :646) and the public method wraps it in queueReadRefusal (:168-183). The callers that pass it: the approver slate (approval-service.ts:3541, with purpose: 'addressing') and the sharing-recipient recompute (sharing-rule-service.ts:1752, :2198) — the D6/D7 bullet left in place is still true.
  • "That posture stays by decision: it is the one the sharing-rule service takes for the same ledger on its own position recipients, so a queue's position and a rule's position cannot disagree during an outage" — TRUE: switchedOff :670-690 catches the sys_metadata_activation read, switches nothing off and warns once, under both answers (refuse is never read there); sharing-rule-service.ts readPositionActive :2318-2339 answers active on the same failure with the same "treated as ACTIVE" warning. Both skip the read when the composition registers no ledger object. Consistent.
  • "Under 'refuse' the registered service reads the queue's own rows (sys_queue, sys_queue_member) first, and a failure there rejects at once" — TRUE: resolve :232-262 reads authoredQueueRow (authored name) and authoredMemberSpecs (both kinds) before any specification; both throw under refuse. Tried: a declared queue reads no sys_queue row (the registry serves it, :235-247) — the sentence names which reads reject, not that both are issued, so it holds. Should-fix (rationale only): "since without them the call does not know what to ask" overstates for a declared queue, whose declared position specs are known from the registry; only the authored additions are not. The behaviour claim stands; the reason would be exact as "since the member set would be incomplete".
  • "Past them it answers true as soon as a read that succeeded reaches userId, even beside a failed read, and rejects only when no successful read reaches userId and one failed" — TRUE: specsReachUser :476-496 returns on the first reaching spec, memoises the first failure, throws it only after the walk, and answers false only with no failure. Tried: a business_unit spec under refuse goes through the memoised expandSpec whose rejected promise is re-thrown into the same walk (:516, :421-424) — same outcome; a switched-off position answers false without a failed read (:511), which the ledger paragraph already covers.
  • Sentences left in place and re-read: the 'nobody' bullet (D3/D4/D5 call with no option — see ③.3), "an EMPTY queue is still []", "false is answered only when every read behind it succeeded" (:494-495), the purpose/onFailedRead independence (newPass takes them as two arguments) — all still true. Nothing the PR leaves standing is false.

Findings. Must-fix: none. Should-fix: the two Seed.locale precision items (rung 1 organization-less loads; the .describe()'s "installing or founding" missing the reseeding user) and the isMember rationale clause — all three are one-clause edits that could ride this PR with one gen:docs regeneration, or a follow-up domain:spec card beside the liveness-note card in ③.6. None states a falsehood a reader would act on wrongly in the direction that matters (every path named filters as described; no filtering path is omitted), so none blocks.

Gate verdicts on this head (every run completed; read 2026-11-05T18:15Z): 65 check-runs — 57 success, 8 skipped, 0 failure. The ten required contexts: Lint & Repo Gates, TypeScript Type Check, Test Core (8/8 shards), Build Core, Governed Surface Queue Guard, Type Check UI, Test UI (16/16), Build & E2E UI, Live E2E UI all success; Lint UI skipped (no ui/ path in the diff). The gates this diff can move — Type Check · source gates (the spec generated artifacts, check:docs included), Lint · docs & code guards, Spec property liveness, Check Changeset — all success. The eight skips are paths-filtered or opt-in jobs (Lint UI, UI Published Artifacts, the dogfood shards and CLI verify, temporal conformance, the schedule gate, the 18.x and packed-tarball opt-ins). These conclusions are the gate verdicts; nothing was run locally.

Implemented-by: claude/issue-30347-spec-docblock-fold
Reviewed-by: session_01L6bhgTqqeHSwZYJmApbDCa

VERDICT: PASS


Generated by Claude Code

… says an organization-less load still reads the environment or global locale setting

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6bhgTqqeHSwZYJmApbDCa
… member set would be incomplete

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6bhgTqqeHSwZYJmApbDCa
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-sales · session_01L6bhgTqqeHSwZYJmApbDCa

Contract review — delta

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 8629666d9d03584174b6f884c44c4b9fb2bf727c
Local-runs: none
Scope: packages/spec/src/data/seed.zod.ts, packages/spec/src/contracts/ownership-queue-service.ts + packages/runtime/src/seed-locale.ts, packages/services/service-settings/src/settings-service.ts, packages/services/service-settings/src/manifests/localization.manifest.ts, packages/core/src/security/organization-reach.ts, packages/core/src/security/resolve-authz-context.ts, packages/cloud-connection/src/marketplace-install-local-plugin.ts, packages/plugins/plugin-sharing/src/ownership-queue-service.ts

Delta of the record at 6665662379 (head 9d61fa4aa, PASS, three should-fixes). Inputs: the four commits since that head, the r1 dev report 6666976543, the r1 PR body, the changeset at this head and the check-runs on 8629666d9. Read-only; mcp_calls: 0.

The merge (871582d63 = 9d61fa4aa + origin/main 1c5cee673)

Clean, no hand-resolved hunk. Against its first parent it touches none of the PR's seven files; against its second parent it reproduces the PR's own delta byte-for-byte (+54/−21, the same seven files); git show --remerge-diff lists no file. The producers the earlier record cited (seed-locale.ts, app-plugin.ts, the marketplace plugin, the sharing service, the organizations plugin) are unchanged between 4831d5006 and this head; the main-side content is not reviewed here. The two moved citations the body re-states (packages.ts apply request now :4000-4007, approval-service.ts 'refuse' now :3541) are correct on this head.

The patch round (871582d63..8629666d9: +15/−10, six files; the changeset untouched)

  • Should-fix 1 — answered. .describe() now reads "then the locale of the installing, reseeding or founding user". TRUE: install and reseed both pass admission.userId (:2525, :4347 → :5108-5110), the replays pass the founder. The four references pages carry that exact string (one occurrence each; package-api.mdx two) — the regeneration is complete.
  • Should-fix 3 — answered. isMember's reason is now "since without them the queue's member set would be incomplete (a declared queue's own specifications come from the registry, but its authored additions come from sys_queue_member)". TRUE: resolve :235-247 builds a declared queue's members as declaredMemberSpecs(declared) plus authoredMembers.get(name), the latter from authoredMemberSpecs (:237, throws under refuse at :646). The behaviour claim around it is unchanged and still holds.
  • Should-fix 2 — answered in its first half, still one source short. The docblock's rung 1 now says "a load that names no organization, such as a single-tenant install or reseed or the restart heal, still reads it, and the environment override or the global setting answers". Checked against the code at this head:
    • "still reads it" — TRUE: seed-locale.ts:137 calls settings.get('localization', 'locale', {}) when no organization is named; the heal names none (:1453), a single-tenant install/reseed resolves none (:5094-5105).
    • "the environment override … answers" — TRUE, and first: settings-service.ts:1951-1963 answers source: 'env' before any row is read (an OS_LOCALIZATION_LOCALE the enumeration rejects contributes nothing, as the comment above it says).
    • "or the global setting answers" — TRUE when a global row exists: resolveKeyFromRows :2119-2135, :2175 builds the chain global → tenant → user → default and takes the first non-null, so a sys_platform_setting row answers source: 'global' even over a tenant row.
    • Omitted: localization is a tenant-scoped namespace (localization.manifest.ts:39), and a caller naming no organization is not read as no organization. organizationReachOf({}) (settings-service.ts:2221-2227) → resolveOrganizationReach (organization-reach.ts:87-95): where no wall stands, ADR-0131 D9 derives the deployment's one organization (resolveGuestOrganization, resolve-authz-context.ts:1676-1696: single posture, exactly one sys_organization row), the reach becomes own and that organization's own localization.locale row answers at the tenant rung (:2137-2149, source: 'tenant', so it counts as set) when no env override and no global row exist. That is the usual single-tenant deployment — one default organization — so a single-tenant install or reseed with a value saved in Setup → Localization seeds in that language. The sentence's "the environment override or the global setting answers" reads as the exhaustive list; the derived organization's own setting is a third. Should-fix (precision, the docblock only — the .describe()'s "the localization.locale setting when set" is right). Suggested parenthetical: "still reads it: the environment override answers first, then the global setting, then — where no wall stands and the deployment holds exactly one organization — that organization's own row". Carrier: this PR if a round 2 is opened for any reason, else fold into spec liveness: the Seed.locale row's note in liveness/seed.json still says the marketplace install passes no locale and that install is locale-neutral by ruling #30607, which already re-anchors the same row's note and producer.
  • Nothing else in the two hits files changed; every sentence judged TRUE in the earlier record is intact.

② Semver and declarations

Changeset unchanged (@objectstack/spec: patch; Clause-②: no; Metadata-protocol: none, both line-initial, the PR body the same). Its two sentences still match the describes: "the user who installs, reseeds or founds the organization" is what the .describe() now says, and "the localization.locale setting, counted only when somebody set it (not source: 'default')" is true under every reach above (an env, global or tenant row is a set value). No shape, default or export moved in the round; patch and none hold. ③ unchanged: the dev's two round-1 deviations (changeset left as is; two moved citations) are both correct; the liveness-note finding is now card #30607 (open, domain:spec, p3).

Gate verdicts on this head (every run completed; read 2026-11-05T19:22Z): 69 check-runs — 59 success, 10 skipped, 0 failure. The ten required contexts: Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard, Type Check UI, Test UI, Build & E2E UI, Live E2E UI all success; Lint UI skipped (no ui/ path). Check Changeset re-ran on the r1 body and is success. The ten skips are paths-filtered or opt-in jobs (Auto Label and Check PR Size on the body re-run, Lint UI, UI Published Artifacts, the dogfood shards and CLI verify, temporal conformance, the schedule gate, the two opt-in harnesses). These conclusions are the gate verdicts; nothing was run locally.

Implemented-by: claude/issue-30347-spec-docblock-fold
Reviewed-by: session_01L6bhgTqqeHSwZYJmApbDCa

VERDICT: PASS — must-fix: none; should-fix: the rung-1 parenthetical's third source, above.


Generated by Claude Code

…th no organization — the deployment's one organization's own setting

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6bhgTqqeHSwZYJmApbDCa
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-sales · session_01L6bhgTqqeHSwZYJmApbDCa

Contract review — delta

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d0863ab931e735fbe32bbf98fa30d8eab5092063
Local-runs: none
Scope: packages/spec/src/data/seed.zod.ts, packages/spec/src/contracts/ownership-queue-service.ts + packages/services/service-settings/src/settings-service.ts, packages/services/service-settings/src/manifests/localization.manifest.ts, packages/core/src/security/organization-reach.ts, packages/core/src/security/resolve-authz-context.ts, packages/core/src/security/system-write-organization.ts, packages/cloud-connection/src/marketplace-install-local-plugin.ts, content/docs/references/api/package-api.mdx

Delta of the records at 6665662379 (9d61fa4aa, PASS) and 6667115115 (8629666d9, PASS, one should-fix). Inputs: the three commits since 8629666d9, the r2 dev report 6667857743, the r2 PR body, the check-runs on this head. Read-only; mcp_calls: 0.

  • Merge e2f2e2175 (8629666d9 + origin/main ffeda137b): no hand-resolved hunk in a PR file. Against its first parent it changes none of the seven; against its second parent it carries the PR's delta; --remerge-diff names one path only, package-api.mdx, as deferred by the merge driver (generated, not text-merged), which is the driver doing its job, not a resolution by hand.
  • 15456e098 regenerates only. It touches package-api.mdx alone (+14/−5). At this head that page differs from origin/main (14c67d446, which contains ffeda137b) in exactly two lines, the two locale rows, and both carry the head's .describe() string byte for byte; every other row is main's own generator output. Nothing else in the merged tree that this page renders differs from main, so the page is what the generator produces from the merged tree; check:docs on this head (the Type Check · source gates run) is the mechanical proof.
  • d0863ab93 — the should-fix answered; the sentence is TRUE and complete. Rung 1 for a load naming no organization now reads: "the environment override answers first, then the global setting, then, where no wall stands and the deployment holds exactly one organization, that organization's own setting". Against the code at this head (none of the cited files moved since 8629666d9): env first, before any row (settings-service.ts:1951-1963); then the chain global → tenant → user → default, first non-null wins (:2119-2135, :2175); the tenant rung (:2137-2149, localization is scope: 'tenant', localization.manifest.ts:39) admits the derived organization's row because organizationReachOf({}) (:2221-2227) → resolveOrganizationReach (organization-reach.ts:87-95: no wall → D9) → resolveGuestOrganization (resolve-authz-context.ts:1676-1696) → resolveSystemWriteOrganization (system-write-organization.ts:115-125: single posture and exactly one sys_organization row give derived; zero or several refuse, so the reach is unowned and no tenant row answers). The order and both conditions in the sentence are the code's. One corner the sentence does not name, recorded and not a finding: a composition that registers no organization object at all (no-organization-object, :120-122) admits a tenant row written with no organization — the lean, organization-less host; a docblock on Seed.locale owes no sentence to that composition. The .describe() and the changeset are unchanged and still match; Clause-②: no, Metadata-protocol: none, @objectstack/spec patch hold.
  • Body r2's re-cited marketplace-install-local-plugin.ts lines spot-checked at this head: :1465 is the heal's runInlineSeed(ctx, datasets, undefined, { app: converted }); :1723-1728 the plugin replayer's locale comment and its runInlineSeed(…, organizationId, { userId: options?.userId }); :1744 const userId = admission.userId in handleInstall; :5239-5242 applySideEffects' runInlineSeed(ctx, datasets, organizationId, { userId: opts.userId, app: manifest }); :5305-5307 the ...(seedLocale ? { locale: seedLocale.locale } : {}) spread. All five land on the code they name.

Gate verdicts on this head (every run completed; read 2026-11-05T20:02Z): 69 check-runs — 59 success, 10 skipped, 0 failure. The ten required contexts: Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard, Type Check UI, Test UI, Build & E2E UI, Live E2E UI all success; Lint UI skipped (no ui/ path). Type Check · source gates (the spec generated artifacts, check:docs on the regenerated page included) and Check Changeset (re-run on body r2) both success. The ten skips are paths-filtered or opt-in jobs. These conclusions are the gate verdicts; nothing was run locally.

Implemented-by: claude/issue-30347-spec-docblock-fold
Reviewed-by: session_01L6bhgTqqeHSwZYJmApbDCa

VERDICT: PASS — must-fix: none; should-fix: none outstanding.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-sales · session_01L6bhgTqqeHSwZYJmApbDCa

维护者速读

席位意见:两张卡(#30347、#30389)合在一个 PR 里收,都只改说明文字。契约复审三轮 PASS:第一轮提了 3 条措辞上的建议,第一轮修补全部落实;第二轮复审又发现「不指定组织的加载」少写了一个取值来源(只有一个组织的部署会用到该组织自己的设置),第二轮修补补上,第三轮确认无遗留。复审发现的台账备注过时,另立了 #30607。

席位核对 — ACCEPT · domain:spec#3 · head d0863ab931e735fbe32bbf98fa30d8eab5092063


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 20:04
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 5, 2026 20:04
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit a839020 Nov 5, 2026
70 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-30347-spec-docblock-fold branch November 5, 2026 20:42
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 protocol:data size/s tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

1 participant