Skip to content

fix(platform-objects,spec)!: sys_organization no longer declares change_slug, whose door only cloud mounts - #24657

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-24318-change-slug-to-cloud
Oct 19, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-24318-change-slug-to-cloud

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #24318

Clause-②: no (narrowing) (a platform object stops declaring an action whose door exists only in cloud; owes a CONTRACT_REVIEW_TIER review on the narrowing face before the queue, and the repo:cloud#2 seat's pointer before landing)

Dev session session_015mj2S1LnzLNur4rJeZuMbN, dispatched by the domain:engine seat 1 (session_01JtSQQ3EWsbNPzyYMug1tXy) under claim 6275837047. Shape B of the release 6255698861: the composition that serves /api/v1/cloud/organizations/{id}/change-slug declares change_slug itself, through the top-level action bound by objectName that PR #24458 attaches, and sys_organization stops declaring it.

What changes

  • packages/platform-objects/src/identity/sys-organization.object.ts: change_slug is removed with its comments. The comments on leave_organization and rename_organization now say what is true: the header carries Invite User, Set Active and Rename Organization inline and Leave Organization in the overflow, and the slug action is declared by the composition that serves its route. The action order is unchanged. defaultRowScope (PR feat(platform-objects)!: the identity objects declare their default row scope, and the registry refuses an extend that sets one #24623's region) is not touched.
  • The four generated locales lose sys_organization._actions.change_slug, regenerated with node scripts/check-i18n-bundles.mjs --write (20 lines removed, nothing else moved).
  • packages/spec/src/kernel/public-auth-features.ts: the multiOrgEnabled.gatedInputs row sys_organization.actions.change_slug is removed. It is the platform objects' own census: feature-gate-guard.test.ts walks only the platform-objects modules, and its forward direction reds when a booked path names an action that no longer exists. It is not a gate on other packages (measured below), so cloud does not need it.
  • packages/platform-objects/src/pages/sys-organization.page.ts: the docblock that listed Change Slug in the header overflow (comment only). Outside the claim's file surface; the removal made it false.
  • Pins and changesets below.

Measured on a booted multi-organization showcase (M)

The harness: bootShowcase({ multiTenant: 'posture-only' }), /auth/config serving multiOrgEnabled: true. The cloud-shaped composition adds one AppPlugin whose stack declares a top-level change_slug with objectName: 'sys_organization' and the platform's former declaration byte for byte (target, method: 'POST', record_header, the one slug param, requiresFeature: 'multiOrgEnabled', description, success message).

Before, on origin/main 1e8b6284c0:

composition change_slug served its visible POST /api/v1/cloud/organizations/org_x/change-slug boot report
showcase once (the platform's) features.multiOrgEnabled != false 404 ENDPOINT_NOT_FOUND none
+ cloud-shaped, identical declaration once (the platform's) same 404 none (sameActionDeclaration reads one declaration)
+ cloud-shaped, label differs once, label Change Slug (the platform's) same 404 one WARN: declared twice, com.objectstack.plugin-auth served, com.measure.cloud-shaped not listed

After, on this branch:

composition change_slug served its visible header position
showcase absent; the record serves create, update, delete, set active, invite, leave and rename n/a n/a
+ cloud-shaped, identical declaration once, attached after the object's own actions features.multiOrgEnabled != false, the same lowering as the platform's own requiresFeature actions after rename, before leave: the ⋯ overflow, where the platform copy sat
+ cloud-shaped, label differs once, the binder's (Change Slug (cloud)) same same

Nothing refuses a non-platform action that declares requiresFeature: 'multiOrgEnabled': the boot succeeds, the sugar is lowered at parse like any action's, and no gatedInputs row is consulted at runtime.

What the cloud repository needs (for the repo:cloud#2 seat's pointer)

  1. One top-level action in cloud's composition, bound by objectName:
{
  name: 'change_slug',
  objectName: 'sys_organization',
  label: 'Change Slug',
  icon: 'edit-3',
  variant: 'secondary',
  mode: 'custom',
  locations: ['record_header'],
  type: 'api',
  target: '/api/v1/cloud/organizations/{id}/change-slug',
  method: 'POST',
  requiresFeature: 'multiOrgEnabled',
  description: 'Renaming the slug rewrites every platform subdomain for this org and parks the old slug for 90 days. Continue?',
  successMessage: 'Organization slug changed',
  refreshAfter: true,
  params: [{ field: 'slug', required: true, defaultFromRow: true }],
}
  1. Its translations, the keys this PR removes from platform-objects' bundles (objects.sys_organization._actions.change_slug):
locale label description successMessage
en Change Slug Renaming the slug rewrites every platform subdomain for this org and parks the old slug for 90 days. Continue? Organization slug changed
zh-CN 修改标识符 重命名标识符会重写该组织所有平台子域名,旧标识符将保留占用 90 天。继续吗? 组织标识符已修改
ja-JP スラッグを変更 スラッグを変更すると、この組織のすべてのプラットフォームサブドメインが書き換えられ、旧スラッグは 90 日間確保されます。続行しますか? 組織のスラッグを変更しました
es-ES Cambiar slug Renombrar el slug reescribe todos los subdominios de la plataforma para esta organización y reserva el slug anterior durante 90 días. ¿Continuar? Slug de la organización cambiado

Whether a translation bundle in cloud's package reaches another package's object's _actions was not measured here; the cloud seat owns that check.
3. Land it before, or with, cloud's pin bump past this PR. Until then cloud's record page has no Change Slug. If cloud declares it while this platform copy is still in its pin, the identical declaration is served once and nothing is reported; a declaration that differs is reported at boot and the platform's copy is the one served.

Pins

Pin sweep: git grep -n "change_slug\|change-slug" over the tree. Every hit outside CHANGELOGs and landed .changeset/pre entries moved:

  • sys-organization-rename-action.test.ts: the header order is now asserted exactly (invite, set active, rename inline; leave in the overflow). The change_slug control is replaced by "every api action targets better-auth's organization/ routes, none the cloud orchestrator, and no change_slug is declared".
  • platform-objects.test.ts: the change_slug lowering row is removed, and a new case holds the matrix's SysOrganization rows equal to every action the object gates on multiOrgEnabled.
  • action-confirm-one-dialog.test.ts: the sys_organization entry leaves the converted map (13 of platform-objects: 16 more actions declare both confirmText and params, so one click opens two sequential dialogs (same shape as #7278) #7309's 14 remain platform declarations).
  • confirm-question-carryover.test.ts: the pair leaves the set, and a new case asserts 13 pairs and that no locale bundle (en included) carries sys_organization.change_slug, with rename_organization as the control.
  • runtime/action-execution-destructive.test.ts: the flipped set is 5 and the full set is 13, and a new case asserts SysOrganization declares no change_slug.
  • qa/dogfood/org-record-admin-reach.dogfood.test.ts: the UNDECIDED hold-out is gone. The served set is asserted equal to the seven graded actions, and the door probe calls every api action the record serves, so the record offers no action whose target answers 404 (the first new pin of the claim).
  • New: qa/dogfood/org-change-slug-bound-action.dogfood.test.ts boots the showcase with the cloud-shaped package and asserts change_slug is served once with the orchestrator target, POST, record_header and its one slug param; its visible equals featureGatePredicate('multiOrgEnabled') and leave_organization's; under celEngine it is admitted with multi-org on and hidden with it off; it sorts after rename and before leave; and the boot names no collision for it (the second new pin).

Verification

Tests (head 91dc52d3e, after merging origin/main 7c7741dcb, unless noted):

  • pnpm --filter @objectstack/platform-objects test: 78 files, 1191 tests passed.
  • pnpm --filter @objectstack/spec test: 701 files, 21313 passed, 1 todo (pre-merge head 048de3158).
  • pnpm --filter @objectstack/runtime test: 425 files, 6668 passed, 19 skipped (pre-merge head 048de3158).
  • dogfood: org-record-admin-reach and org-change-slug-bound-action: 2 files, 12 tests passed.
  • pnpm --filter @objectstack/core test:repo: 6 files, 82 passed.
  • typecheck, exit 0 on each: @objectstack/platform-objects, @objectstack/spec, @objectstack/runtime, @objectstack/dogfood (after the merge).
  • pnpm --filter @objectstack/spec check:generated: all 14 artifacts up to date (after the merge and a spec rebuild). node scripts/check-i18n-bundles.mjs: exit 0.

Ablation (scripts/ablation-replace.mjs, trap-restored; dist proved by scripts/ablation-dist-preflight.mjs): the platform copy put back into sys-organization.object.ts and platform-objects rebuilt (marker present in 6 built files):

  • dogfood org-record-admin-reach: red, 2 cases ("every grade is served the same action set", and "every api action ... none answers ENDPOINT_NOT_FOUND").
  • platform-objects: red, 4 cases (the rename file's header order and api-target cases, the matrix completeness case, and feature-gate-guard's reverse direction, which reds because the row is gone).
  • runtime: red, 1 case ("sys_organization no longer declares change_slug, the sixth").
  • org-change-slug-bound-action stays green under the ablation, as expected: with an identical platform copy present the served declaration is the same bytes.

Restored: git diff HEAD empty and the blob hash equals HEAD's; rebuilt, marker absent from all 66 built files; the dogfood pair green again (12/12).

Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 88 families; all 88 ran on 91dc52d3e with exit 0, and --ran reconciled "88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero)". check:dual-build-cjs-loads first exited 3 (eight packages had no dist/); they were built and it passed. The last commit, 44da7fe8d, touches only the two changesets (the claim's Clause-② line carried verbatim), and the nine changeset families were re-run on it, all exit 0.

Acceptance notes

  • sys-organization.page.ts is outside the claim's File surface: list; its docblock named Change Slug in the header overflow, which this change made false.
  • The new dogfood file is not the ablation target for the platform copy; org-record-admin-reach is, and it reds.
  • Cloud-side items not measured here: whether cloud's translation bundle reaches sys_organization._actions of another package's object; cloud's own boot with the declaration.

Generated by Claude Code

…ge_slug, whose door only cloud mounts

The platform object declared `change_slug` at `record_header`, gated on
`multiOrgEnabled`, with a target (`/api/v1/cloud/organizations/{id}/change-slug`)
no package in this repository serves. An open-source multi-organization
composition offered Change Slug and answered `404 ENDPOINT_NOT_FOUND`.

The composition that serves the route declares the action itself, bound to
`sys_organization` by `objectName`, which the registry attaches to the
served object. The four generated locales are regenerated, and
`multiOrgEnabled`'s `gatedInputs` row for the action, the platform objects'
own census, is removed.

Claude-Session: https://claude.ai/code/session_015mj2S1LnzLNur4rJeZuMbN
Co-authored-by: Claude <noreply@anthropic.com>
…s_organization, and a cloud-shaped binding served gated

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

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/platform-objects, @objectstack/spec, touching 6 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/platform-objects/src/pages/sys-organization.page.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/automation/flows.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/deployment/cli.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/deployment/seed-tenancy-repair.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/getting-started/build-with-claude-code.mdx (via record_header (literal, a string literal in actions))
  • content/docs/permissions/administrator-guide.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/permissions/sso.mdx (via multiOrgEnabled (symbol, a field of const object PUBLIC_AUTH_FEATURES), multiOrgEnabled (literal, a string literal in actions))
  • content/docs/protocol/kernel/config-resolution.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/protocol/objectql/schema.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/protocol/objectui/actions.mdx (via record_header (literal, a string literal in actions))
  • content/docs/protocol/objectui/layout-dsl.mdx (via record_header (literal, a string literal in actions))
  • content/docs/ui/actions.mdx (via record_header (literal, a string literal in actions))

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

  • content/docs/releases/implementation-status.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/releases/v17/17-1.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/releases/v17/17-4.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/releases/v17/17-5.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))

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

Coarse fallback — 143 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 bfc1539fe00a33bc3215614a8d6c76a05eb8e8aa → packageMentionDocs.

Which tree this was computed on

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

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

⚠️ 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 bfc1539fe00a33bc3215614a8d6c76a05eb8e8aa → 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: 44da7fe8d4186486dd5be4eeef6e7292465382ce
Local-runs: none
Scope: packages/spec/src/kernel/public-auth-features.ts, packages/platform-objects/src/identity/sys-organization.object.ts, packages/platform-objects/src/pages/sys-organization.page.ts, packages/platform-objects/src/apps/translations/en.objects.generated.ts, packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts, packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts, packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts, packages/platform-objects/src/apps/translations/confirm-question-carryover.test.ts, packages/platform-objects/src/identity/action-confirm-one-dialog.test.ts, packages/platform-objects/src/identity/sys-organization-rename-action.test.ts, packages/platform-objects/src/platform-objects.test.ts, packages/runtime/src/action-execution-destructive.test.ts, packages/qa/dogfood/test/org-record-admin-reach.dogfood.test.ts, packages/qa/dogfood/test/org-change-slug-bound-action.dogfood.test.ts, .changeset/24318-platform-objects-change-slug-to-cloud.md, .changeset/24318-spec-change-slug-gated-input.md + packages/objectql/src/registry.ts (sameActionDeclaration, finishServedObject, attachBoundActions, boundCodeActions, FIND_BOUND_ACTION_COLLISIONS, describeBoundActionCollision), packages/objectql/src/plugin.ts (the kernel:ready seal and reportBoundActionCollisions), packages/spec/src/ui/action.zod.ts (requiresFeature enum and the lowerRequiresFeature transform), packages/platform-objects/src/feature-gate-guard.test.ts, packages/spec/src/kernel/public-auth-features.test.ts, packages/plugins/plugin-auth/src/public-feature-registry.test.ts, packages/platform-objects/src/identity/sys-organization-invite-action.test.ts (headerOrder), packages/platform-objects/scripts/i18n-extract.config.ts, scripts/check-i18n-bundles.mjs, package.json (check:i18n, i18n:extract), scripts/pm/clause2-line.mjs, scripts/check-adr-0087-registration.mjs (readDisposition), scripts/check-changeset-no-major.mjs (launch-window guard, RC exemption), .changeset/pre.json, .changeset/pre/24271-org-rename-door.md, .changeset/pre/24384-platform-objects-org-admin-reach.md, docs/adr/0133-org-management-open-basics.md, .github/workflows/ci.yml (filter, dogfood, dogfood-gate), .github/workflows/lint.yml (typecheck-consumers), .github/workflows/pr-automation.yml (Check Changeset), packages/verify/src/harness.one-composition.test.ts (extraPlugins), packages/qa/dogfood/test/showcase-boot.ts, packages/core/src/fallbacks/memory-i18n.ts (loadTranslations), objectui packages/components/src/renderers/layout/containers.tsx (the header order sort and the maxVisible split, read only as the PR body states it), content/docs/** (no change_slug / change-slug hit on the head). Diff read as git diff 7c7741dcb4 pr/24657 (merge base of origin/main and the head). Check-runs read at 2026-10-19T22:13Z: 41 runs, every completed run success or skipped; Test Core (the aggregate) queued, still running at this read, with its six shards success.

① Derived judgments

The removal — right. The diff's three hunks in sys-organization.object.ts are a comment on leave_organization (202–210), a comment on rename_organization (226–240) and the change_slug block (255–290); nothing else in the file moves, so create_organization, update_organization, delete_organization, set_active_organization, invite_user, leave_organization and rename_organization are byte-identical in declaration, declared order and gate. Every remaining type: 'api' target is under /api/v1/auth/organization/, a better-auth door the open-source composition mounts; the only /api/v1/cloud/ target left the object. On an open-source multi-org composition the record page therefore offers no action whose target answers 404. The object's comments are true under the console's order model (objectui containers.tsx: order ascending with unset read as 0, variant: 'primary' tie-break, then stable registration order; the record header is invite_user at order -1, set_active_organization 0, rename_organization 0, leave_organization 1; maxVisible 3 puts leave alone in the overflow's destructive band). The page docblock now says the same and is true.

The cloud-shaped binding — right, with one unstated qualifier. attachBoundActions runs in finishServedObject, the tail of every object read (resolveObject, the code layer, the stored-row and MetadataService folds), and attaches by reference every standalone action a code package ships whose standaloneActionOwnerKey is the object's name and whose package is not disabled, deduplicated by name, after the object's own actions, the attached ones stable-sorted by order among themselves. A top-level action is parsed by the same ActionSchema whose transform lowers requiresFeature to visible (lowerRequiresFeature(lowerRequiresMembershipReach(data)), features.* last), so the served visible is features.multiOrgEnabled != false, identical to leave_organization's, with no gatedInputs row consulted. Target, method, location and the one slug param travel by reference. Header position under the console's sort: invite (-1), set_active (0), rename (0), change_slug (0, registered after the object's own actions), leave (1); so change_slug sits in the overflow, in the non-destructive band ahead of leave, exactly where the platform copy sat. The pin's fixture is the removed declaration's fourteen keys and values unchanged plus objectName, which sameActionDeclaration excludes as a provenance key: a faithful copy. Qualifier the PR body does not carry: this is the synthesized header. Cloud's own organization page lists its header actions explicitly (actions: ['leave_organization', 'change_slug'], per #24271's body), and a page:header resolves ids against its own object's served actions (ruling 6228318638, B′ on #24034), which now carry the attached action; on cloud the position is the page list's, not order's, and the resolution is unmeasured there.

The transition — right. While cloud's pin still carries the platform copy: attachBoundActions skips the name (present.has), so the platform copy is served; FIND_BOUND_ACTION_COLLISIONS skips a pair that sameActionDeclaration holds equal (structural equality minus _-stamps, protection, objectName), so an identical cloud declaration is served once with no report; a differing one is reported at kernel:ready by reportBoundActionCollisions as one warn per pair naming the object, the action and both packages, the boot goes on, and the object keeps serving its own (#24472's rung 1, the maintainer's "18.x, report first", 6262054499). A type: 'api' action registers no handler, so the handler-face warning does not fire for this pair. The PR body's section 3 and the platform-objects changeset's Fix line tell cloud exactly this.

gatedInputs — right. Readers of the rows on the head: feature-gate-guard.test.ts (walks identity, metadata, system of packages/platform-objects only) and public-auth-features.test.ts (grammar and the gates-XOR-exempt rule). featureGatePredicate and lowerRequiresFeature read semantics; plugin-auth's public-feature-registry.test.ts reads the key set and semantics; requiresFeature is z.enum(PUBLIC_AUTH_FEATURE_NAMES), keys not rows; no packages/lint rule, runtime path or gate reads gatedInputs to admit or refuse any action. The row is the platform objects' own census. The guard keeps teeth: forward is it.each over the 38 remaining rows (39 on the base), reverse is it.each over every features.* reference with a floor of 37 (38 references on the head); restoring only the platform copy reds reverse (an unbooked features.multiOrgEnabled reference), restoring only the row reds forward (no such action), which is the dev's ablation reading.

The translations — right. Each of the four bundles loses exactly the change_slug subtree (label, description, successMessage, five lines) at the same position; no other key moves. The generator (os i18n extract over scripts/i18n-extract.config.ts, the committed bundle as merge baseline, --fill=default --source-hashes) emits only the keys the schema walk yields, so a removed action's subtree leaves every locale, en included, and no source-hashes companion carried change_slug on the base. The mechanical proof is on the head: pnpm check:i18n (self-test plus check-i18n-bundles.mjs, verdict 1 = bundle drift) runs in lint.yml's typecheck-consumers under continue-on-error, whose outcome the lane-end OS_LANE_STEPS reader at lint.yml:6612 re-reads; that check-run, Type Check · consumer gates, is success.

The pins — right, with one bare deletion and one CI gap. sys-organization-rename-action.test.ts asserts the exact header order, the sorted seven-name api set with every target matching ^/api/v1/auth/organization/[a-z-]+$, and change_slug absent. platform-objects.test.ts adds a completeness pin: the set of actions whose visible.source names features.multiOrgEnabled equals the matrix's SysOrganization rows. confirm-question-carryover.test.ts pins 13 pairs and change_slug absent from all five bundles with rename_organization as the control. action-execution-destructive.test.ts pins the flipped set at 5, the full set at 13 and change_slug absent. org-record-admin-reach drops the UNDECIDED hold-out, pins the served set equal to the seven graded actions and probes every api action the record serves. Judged from the code, with the platform copy restored that file reds in two cases: the served set gains change_slug (strict toEqual), and the probe POSTs /cloud/organizations/org_does_not_exist/change-slug, the router answers 404, and both the status === 404 filter and the name-set equality fail; the dev's "red 2" reading holds. org-change-slug-bound-action asserts served once, target, method, location, param, visible equal to featureGatePredicate('multiOrgEnabled') and to leave's, admitted/hidden under celEngine, the header order with change_slug between rename and leave, and no WARN/ERROR line naming 'change_slug'; under the ablation it stays green by construction, as reported. action-confirm-one-dialog.test.ts is the one bare deletion (the map loses its row, no new assertion); the absence is pinned in four other files, so it is acceptable. Pin-quality note, not a verdict ground: the new file's "no collision" case first asserts that some WARN line was captured at all, so it depends on an unrelated boot warning being present. CI gap: see ③.

② Semver level

  • Packages and levels — right. @objectstack/platform-objects: minor and @objectstack/spec: minor. Both are breaking narrowings; under the launch-window convention (check-changeset-no-major.mjs:58, :1670: a (narrowing) is BREAKING and ships minor) and with the RC exemption not active (see the next item) a major would be refused, so minor is the enforced grade. Check Changeset (pr-automation.yml: check-empty-changeset, check-adr-0087-registration, check-changeset-no-major, each with --self-test) is success on the head.
  • BREAKING banner — present in both, FROM / TO / Fix each on its own bullet; the counts are right (seven sys_organization rows to six; the eight served actions to seven). Clause-②: no (narrowing) (...) opens a line in both changesets and in the PR body. readClause2Line reads value no and arm narrowing: matchValueToken stops at no before a non-word character, readArmToken matches (narrowing and reads nothing after it, so the trailing parenthetical is ignored; no (narrowing) is not the refused no (widening) contradiction.
  • ADR-0087 — right. Exactly one adr-0087: not-required (no-migration-prescription) ... marker per changeset; readDisposition parses category no-migration-prescription with the rest as the why; the gate is green, so the FROM / TO / Fix bullets were not read as a migration prescription. The category is the honest one: no authorable key, export or stored shape moves and nothing is rewritten; the repair is another composition's declaration.
  • Widening — none. No new export, public member or accepted key: the engine (FIND_BOUND_ACTION_COLLISIONS, attachBoundActions) is untouched, the test files add no surface, PUBLIC_AUTH_FEATURES keeps its keys and entry type. The spec removal is a narrowing a consumer can feel: the constant is as const, so typeof PUBLIC_AUTH_FEATURES.multiOrgEnabled.gatedInputs loses a literal member and the runtime array loses a string; the spec changeset says both. No generated artifact mirrors the census (the row's siblings appear only in the source file), so check:generated and the api-surface baselines are unaffected, consistent with Spec property liveness and the lint lanes green.
  • One false statement in a changeset body — the ground of the verdict. .changeset/24318-platform-objects-change-slug-to-cloud.md line 13 says the change ships as minor "under the launch-window convention for breaking changes (.changeset/pre.json is open in next pre mode)". On this head .changeset/pre.json is {"mode": "exit", "tag": "next"} (origin/main c46b5f14cf, "exit Changesets pre mode (next), so the next version pass cuts the 18.0.0 GA", chore(release): exit Changesets pre mode (next), so the next version pass cuts the 18.0.0 GA #24555): pre mode is not open. The sentence also has the mechanism backwards: check-changeset-no-major.mjs:105–110 names the pre-mode state as the RC EXEMPTION under which a major is admitted; it is the exited state, this head's, under which the launch-window guard is armed and a breaking change ships minor. changeset version compiles this paragraph into packages/platform-objects/CHANGELOG.md, which AGENTS.md marks RELEASE-OWNED: a factual error in a released entry is amended only in a dedicated docs-only PR and never in a code PR. The spec changeset's parallel sentence carries no pre-mode claim and is right. Fix before the queue: delete the parenthetical, or replace it with "pre mode exited at c46b5f14cf; the launch-window convention ships a breaking change as minor". One line, no code, no re-measurement.

③ Boundary flags

  • sys-organization.page.ts outside the claim's File surface — accepted. The fold-in note 6249926047 named this docblock for this card's touch, the first claim 6254536343 listed the file, and the removal made the sentence false; two lines, comment only, true now.
  • Spec and runtime full suites on the pre-merge head 048de3158 — accepted. The merge 91dc52d3e brought four main commits, none touching the diff's files; on the head, Test Core shards 1–6 are success and the aggregate is queued at this read, so the check-runs answer this family for the head once the aggregate settles.
  • Changesets carrying the claim's whole Clause-② line with its parenthetical — accepted. Read as declared / no / narrowing by the single reader (clause2-line.mjs), identical on the PR body and both changesets, and Check Changeset is green.
  • The new dogfood pin green under the ablation by construction — accepted. An identical platform copy serves the same bytes and sameActionDeclaration holds; the ablation target is org-record-admin-reach, which reds in two cases by the code above, and the PR body says so.
  • Added: the dogfood family has no CI verdict on this head. ci.yml's two speeds (skills(ci): two-speed CI — fast set on PR and merge-queue runs, full battery hourly on main and before a release, main-red stops the lane, four required contexts plus the guard, timing gates report-only, test budgets not deletions #22998): the dogfood shards carry github.event_name != 'pull_request' && github.event_name != 'merge_group', so they skipped here (Dogfood Regression Gate (matrix.shard/3): skipped), and dogfood-gate (Dogfood Regression Gate: success) keeps if: always() and concludes success on zero expected attestations. Neither this PR's run nor the merge group runs the moved pin or the new one; their first CI measurement is main's hourly full battery after landing, read by the publish lane. The dev's local 12/12 is the only run, read as a claim; both files are judged right from the code above. The seat accepts that claim or asks the dev for the two-file dogfood run on the head; this record did not probe.
  • Added: a bare deletion and a brittle sanity check (①, the pins): action-confirm-one-dialog.test.ts loses a row with no new assertion, covered elsewhere; the new pin's bootLines.some(WARN) precondition rests on an unrelated warning.
  • Open question 1 (header position of cloud's copy): A. By the four axes: business need, cloud wants Change Slug where it was, and A gives that under the synthesized header; long-term soundness, A is the only shape that is sameActionDeclaration-equal to the platform copy, so B's explicit order would be a DIFFERING declaration on every cloud boot until the pin bump, one warn naming com.objectstack.plugin-auth each boot with the platform copy served under its own order anyway; AI safety, one copied declaration is the hardest to get wrong; startup scope, no new requirement. And on cloud's page the header is an explicit actions list (platform-objects sys_organization: a record page has no door to rename an organization — update_organization is list_item only and bundles slug, which the cloud's change_slug orchestrates #24271), so B buys nothing there either. The dev's recommendation stands, with the stronger reason it did not give.
  • Open question 2 (does cloud's bundle reach sys_organization._actions): A. The in-memory i18n service deep-merges every loadTranslations(locale, data) into one tree per locale (memory-i18n.ts:182–189), platform plugins push theirs at kernel:ready, and resolveKey walks objects.sys_organization._actions.change_slug.label whichever package pushed it; the console reads the key through the shared localizer with the authored literal as fallback (containers.tsx, resolveLabel), so a miss degrades to the English declaration, never to a broken button. The cloud seat measures on cloud's boot; a card (class a) only on a measured miss. B is premature.
  • Cross-lane surface — what the seat declares before the queue. To domain:spec: packages/spec/src/kernel/public-auth-features.ts, one row leaving the published as const census, @objectstack/spec: minor, Clause-②: no (narrowing), no key, enum value, export or generated artifact moves, the platform objects' guard the only reader. To domain:cli: packages/runtime/src/action-execution-destructive.test.ts (flipped set 6 to 5, full set 14 to 13, absence pinned), packages/qa/dogfood/test/org-record-admin-reach.dogfood.test.ts (the UNDECIDED hold-out removed, the probe now covers every served api action) and the new packages/qa/dogfood/test/org-change-slug-bound-action.dogfood.test.ts (a booted pin mounting an AppPlugin through the harness's extraPlugins), with the note that the dogfood family runs off the per-PR path, so a first main-red there is that lane's to read.
  • The cloud pointer — what the repo:cloud#2 seat ([PM seat] repo:cloud · seat 2 — ⏳ vacant #24145) must be told before this lands. (1) The declaration, byte for byte as the PR body's block: a top-level action in a code package, objectName: 'sys_organization', name: 'change_slug', type: 'api', target: '/api/v1/cloud/organizations/{id}/change-slug', method: 'POST', locations: ['record_header'], requiresFeature: 'multiOrgEnabled', mode: 'custom', variant: 'secondary', icon: 'edit-3', the description, successMessage: 'Organization slug changed', refreshAfter: true, params: [{ field: 'slug', required: true, defaultFromRow: true }], no order (see open question 1), in a package that is not disabled; a target object no package registers fails the boot, which sys_organization is not. (2) The four locales' strings under objects.sys_organization._actions.change_slug.{label, description, successMessage}, exactly the PR body's table, shipped in cloud's bundle; reach measured on cloud's boot, English literal as the fallback. (3) The transition: before cloud's framework pin passes this PR, an identical declaration is served once (the platform's) with no report; a differing one is one warn per boot ("Action 'change_slug' on object 'sys_organization' is declared twice, and the two declarations differ ...", com.objectstack.plugin-auth serving) with the platform copy served; after the bump cloud's copy is the only one, attached after the object's own actions; land the declaration before or with the bump, or cloud's record page has no Change Slug in between. (4) Missing from the PR body's section: cloud's organization page names change_slug in an explicit page:header actions list, so cloud must verify the id resolves against the served object (it carries the attached action, by finishServedObject; unmeasured on cloud) and that the header position there is the page's, not order's; cloud must not add a gatedInputs row for its action (the census is platform-only and its forward guard would red on an action the platform does not declare); the change-slug route must stay mounted at the same path; type: 'api' registers no handler, so no handler-face warning; and this lands as minor of @objectstack/platform-objects and @objectstack/spec with pre mode exited, so the 18.x cloud pins to is the one that carries it.

Implemented-by: claude/issue-24318-change-slug-to-cloud
Reviewed-by: session_01JtSQQ3EWsbNPzyYMug1tXy

VERDICT: FAIL

Seat note · domain:engine seat 1 · 2026-10-19T22:17Z

The seat spot-checked the record's central claims on this head:

Fix round 1 (sent to the dev):

  • correct that one changeset line;
  • run the two dogfood files on the new head, since the dogfood family does not run on a pull request;
  • optionally, make the new pin's "no collision" case stand without depending on an unrelated boot warning.

A delta record follows the fix round.


Generated by Claude Code

…state, and the no-collision case carries its own positive control

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

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 473c6ff949110fbc6f4c5e12ee3116cc895128a1
Local-runs: none
Scope: packages/spec/src/kernel/public-auth-features.ts, packages/platform-objects/src/identity/sys-organization.object.ts, packages/platform-objects/src/pages/sys-organization.page.ts, packages/platform-objects/src/apps/translations/en.objects.generated.ts, packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts, packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts, packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts, packages/platform-objects/src/apps/translations/confirm-question-carryover.test.ts, packages/platform-objects/src/identity/action-confirm-one-dialog.test.ts, packages/platform-objects/src/identity/sys-organization-rename-action.test.ts, packages/platform-objects/src/platform-objects.test.ts, packages/runtime/src/action-execution-destructive.test.ts, packages/qa/dogfood/test/org-record-admin-reach.dogfood.test.ts, packages/qa/dogfood/test/org-change-slug-bound-action.dogfood.test.ts, .changeset/24318-platform-objects-change-slug-to-cloud.md, .changeset/24318-spec-change-slug-gated-input.md + this delta's cited files: .changeset/pre.json, scripts/check-changeset-no-major.mjs (judge, the RC-exemption docblock, the exempt reporting comment), commit c46b5f14cf (its .changeset/pre.json hunk), packages/core/src/logger.ts (ObjectLogger constructor defaults, write), packages/core/src/kernel.ts (createLogger(config.logger)), packages/objectql/src/plugin.ts (reportBoundActionCollisions, the [ObjectQLPlugin] info lines on the start path), packages/objectql/src/registry.ts (describeBoundActionCollision), packages/qa/dogfood/vitest.config.ts (disableConsoleIntercept, OS_REGISTRY_LOG), packages/verify/src/harness.ts (new ObjectKernel()); and the baseline record's cited set carried over unchanged: packages/objectql/src/registry.ts (sameActionDeclaration, finishServedObject, attachBoundActions, boundCodeActions, FIND_BOUND_ACTION_COLLISIONS), packages/objectql/src/plugin.ts (the kernel:ready seal), packages/spec/src/ui/action.zod.ts (requiresFeature enum, lowerRequiresFeature), packages/platform-objects/src/feature-gate-guard.test.ts, packages/spec/src/kernel/public-auth-features.test.ts, packages/plugins/plugin-auth/src/public-feature-registry.test.ts, packages/platform-objects/src/identity/sys-organization-invite-action.test.ts (headerOrder), packages/platform-objects/scripts/i18n-extract.config.ts, scripts/check-i18n-bundles.mjs, package.json (check:i18n, i18n:extract), scripts/pm/clause2-line.mjs, scripts/check-adr-0087-registration.mjs (readDisposition), .changeset/pre/24271-org-rename-door.md, .changeset/pre/24384-platform-objects-org-admin-reach.md, docs/adr/0133-org-management-open-basics.md, .github/workflows/ci.yml (filter, dogfood, dogfood-gate), .github/workflows/lint.yml (typecheck-consumers), .github/workflows/pr-automation.yml (Check Changeset), packages/verify/src/harness.one-composition.test.ts (extraPlugins), packages/qa/dogfood/test/showcase-boot.ts, packages/core/src/fallbacks/memory-i18n.ts (loadTranslations), objectui packages/components/src/renderers/layout/containers.tsx (read only as the PR body states it), content/docs/**. Delta read as git diff 44da7fe8d pr/24657d: one commit, 473c6ff949, parent 44da7fe8d4186486dd5be4eeef6e7292465382ce, the baseline record's head. The whole-PR diff base is unchanged: the merge base of origin/main and the head is still 7c7741dcb4. Check-runs read at 2026-10-19T23:06Z: 41 runs, 35 success, 6 skipped, none running. At a first read 2026-10-19T23:00Z, Test Core (3/6) and Test Core (4/6) were in_progress and the aggregate Test Core was not yet listed; at the second read both shards and the aggregate are success.

This is a delta review on record 6278606056 (head 44da7fe8d, FAIL on ② alone: the platform-objects changeset's BREAKING line claimed .changeset/pre.json is open in next pre mode). It judges the one commit on top of that head and states which of the baseline's judgments carry over.

① Derived judgments

The delta — two hunks and nothing else. git diff --stat 44da7fe8d pr/24657d: .changeset/24318-platform-objects-change-slug-to-cloud.md (+1/−1, line 13) and packages/qa/dogfood/test/org-change-slug-bound-action.dogfood.test.ts (+3/−1, lines 150–152). No other path moves. The object, the page docblock, the four bundles, the spec constant, the other seven pins, the spec changeset and the engine are byte-identical to 44da7fe8d, so every baseline judgment that reads them carries over unchanged: the removal, the cloud-shaped binding (with its unstated qualifier about the synthesized header), the transition, gatedInputs, the translations, and the pins other than the one case below.

The new parenthetical — true, word by word. Line 13 now reads: "A declared action leaves a published platform object, shipped as minor under the launch-window convention for breaking changes (pre mode was exited at c46b5f14cf, so the launch-window guard is armed and a breaking change ships as minor)."

  • "pre mode was exited at c46b5f14cf": commit c46b5f14cf6be24cc30f83342b690411933a85b3 ("chore(release): exit Changesets pre mode (next), so the next version pass cuts the 18.0.0 GA", chore(release): exit Changesets pre mode (next), so the next version pass cuts the 18.0.0 GA #24555) carries exactly one hunk on .changeset/pre.json, "mode": "pre" to "mode": "exit", tag next unchanged. It is an ancestor of this head and the last writer of pre.json on it (git log pr/24657d -- .changeset/pre.json: c46b5f14cf, before it a87d8be299, the enter). The file on the head reads {"mode": "exit", "tag": "next"}; so does the file on current origin/main (7c509c286f). The ten-hex prefix resolves uniquely.
  • "so the launch-window guard is armed": scripts/check-changeset-no-major.mjs on the head names its first question the LAUNCH-WINDOW GUARD (docblock line 5). Its RC EXEMPTION is "mode": "pre" and nothing else (lines 105–110: the guard "stands aside for the duration and re-arms automatically once pre-mode is exited"; lines 291–293: absent, "exit" and unreadable all enforce). The code agrees: judge() at line 673 returns exempt only when pre?.mode === 'pre', otherwise enforce; the exempt reporting comment (803–807) says changeset pre exit rewrites the mode to "exit", "which is not pre". With pre.json at exit, the guard enforces; "armed" is the right word.
  • "and a breaking change ships as minor": lines 58–59 ("During the launch window we ship breaking changes as minor") and 78–79; the convention's end condition is GA (64–76), which the exit of pre mode sets up but which no version pass has yet cut, and the guard itself is "what gets disarmed at GA" (94–100) and is present and enforcing on this head.
  • The sentence now has the mechanism the right way round: exited, therefore armed, therefore minor; the baseline's complaint was the reverse reading. Check Changeset is success on the head, but that green is not what makes the sentence true: with two minor changesets the guard's verdict is clean and the exemption branch is never consulted; the truth rests on the two files read above. Pin-quality note, not a ground: the sentence says "ships as minor" twice. It carries no model identifier and no angle bracket.

Nothing else in either changeset moves. Platform-objects changeset on the head: frontmatter '@objectstack/platform-objects': minor (line 2), Clause-②: no (narrowing) (...) (line 7), the BREAKING banner with FROM / TO / Fix each on its own bullet (13–17), the Unchanged paragraph (19) and the one adr-0087: not-required (no-migration-prescription) ... marker comment (21) are byte-identical to the baseline head except for the one parenthetical. Spec changeset: not in the diff; '@objectstack/spec': minor, Clause-②: no (narrowing) (...) (line 7), banner and bullets (11–15), one marker comment (19), unchanged.

The dogfood pin's positive control — it has teeth, and the right ones. The old precondition bootLines.some(/ WARN /) rested on an unrelated boot warning (the baseline's pin-quality flag). The new one is bootLines.some(...) over line.includes('[ObjectQLPlugin]'), and from the code it proves exactly what the negative assertion needs: that the spy sees the channel a collision report would travel.

  • A collision report is reportBoundActionCollisions (plugin.ts 2894–2898): one ctx.logger.warn per pair whose message is the literal [ObjectQLPlugin] followed by describeBoundActionCollision(collision), which opens "Action '${action}' on object '${objectName}' is declared twice, and the two declarations differ: ..." (registry.ts 189–197). So a collision line carries both the control's token and 'change_slug' in single quotes, at level warn.
  • The kernel logger (packages/core/src/logger.ts, write, 337–394) sends debug, info and warn to process.stdout.write and only error/fatal to stderr (line 351), in pretty format ts LEVEL [name] message (370–374); the console fallback (387–393) is reached only when process streams are absent. The control's info lines and a collision's warn line therefore pass through the same method on the same object the test replaces (vi.spyOn(process.stdout, 'write'), lines 91–94), and / (WARN|ERROR) / matches the pretty head's WARN. Vitest's console interception is not in the path, and both dogfood projects set disableConsoleIntercept: true anyway (vitest.config.ts 178, 366).
  • The control lines exist on the boot path at info: [ObjectQLPlugin] Bridging i18n service to ObjectQL for validation messages (plugin.ts 1113, when an i18n service is registered, which the platform's i18n plugin is), [ObjectQLPlugin] Bridging realtime service ... (1096), [ObjectQLPlugin] subscribed to object metadata events (ADR-0008 PR-7) (1439). The harness builds the kernel as new ObjectKernel() (harness.ts 957) with no logger config, so createLogger(config.logger) (kernel.ts 242) takes the defaults level: 'info', format: 'pretty' (logger.ts 236–237); OS_REGISTRY_LOG: 'warn' quiets the SchemaRegistry's own [Registry] lines, not this logger. The token is a literal in those message strings, not the logger's name label (the plugin's name is com.objectstack.engine.objectql), so the control matches precisely the prefix a collision line would carry.
  • Mutating the marker to a string no boot prints makes .some false and the case red: structurally certain from the assertion; the run that showed it is the dev's claim (③).
  • The negative assertion is unchanged: bootLines.filter(...) over line.includes("'change_slug'") && / (WARN|ERROR) /.test(line), expected toEqual([]), byte-identical to the baseline head. Residual pin-quality note, not a ground: ERROR lines go to stderr, which the stdout spy does not capture, so the ERROR arm of the regex is inert on this channel; the collision report is a warn, so the live arm is the right one. The baseline's "brittle sanity check" flag is resolved.

Carry-over. The baseline's ① judgments on the removal, the binding, the transition, gatedInputs, the translations and the pins read bytes this delta does not touch and hold on this head as written there, including the one bare deletion (action-confirm-one-dialog.test.ts, covered in four other files) and the qualifier that cloud's own organization page lists its header actions explicitly.

② Semver level

  • Packages and levels — unchanged and right. @objectstack/platform-objects: minor and @objectstack/spec: minor; both narrowings; the guard is armed on this head (①), so minor is the enforced grade, and the platform-objects changeset now says so for the right reason. Check Changeset (pr-automation.yml: check-empty-changeset, check-adr-0087-registration, check-changeset-no-major, each with --self-test) is success on the head.
  • BREAKING banner — present in both, FROM / TO / Fix each on its own bullet, counts unchanged (seven sys_organization rows to six; eight served actions to seven). The one line that moved is the platform-objects banner's parenthetical, now true. The spec changeset's parallel sentence carries no pre-mode claim and is unchanged.
  • Clause-②: no (narrowing) (...) opens a line in both changesets, byte-identical to the baseline head, read declared / no / narrowing by clause2-line.mjs as before. The dev states the PR body was not edited; the body is not among this record's inputs and that stands as a claim, consistent with Check Changeset and Governed Surface Queue Guard both success on the head.
  • ADR-0087 — unchanged. One adr-0087: not-required (no-migration-prescription) ... marker per changeset, byte-identical; the gate is green on the head.
  • Widening — none. The delta adds one prose sentence and one test assertion: no export, public member or accepted key.
  • The baseline's FAIL ground is removed. The false statement that changeset version would have compiled into the RELEASE-OWNED packages/platform-objects/CHANGELOG.md is replaced by a true one; no other changeset byte moved.

③ Boundary flags

  • The delta adds no boundary. Both touched files are inside the claim's surface (the card's changeset and the card's new pin); the fix round did what record 6278606056's seat note asked and nothing beyond it.
  • The dogfood family still has no CI verdict on this head. Dogfood Regression Gate (${{ matrix.shard }}/3) is skipped and Dogfood Regression Gate is success on zero expected attestations, as on the baseline head (ci.yml's two speeds). The first CI measurement of the moved pin and the new one remains main's hourly full battery after landing, read by the publish lane. The dev's run is the only run of the two files on this head, read as a claim (next item).
  • The dev's claims (report 6278779250), sorted.
    • 7/7 and 5/5 on head 473c6ff94 — claims; not confirmable read-only. The arithmetic is confirmed from code: org-record-admin-reach.dogfood.test.ts (untouched by the delta) has four it cases plus it.each over the three grades owner, admin, member, seven in all; the new file has five it cases. The only assertion that changed is the positive control, judged true from the code above provided the boot registers the services whose bridging lines it looks for, which the platform composition does.
    • The control mutation reds — structurally certain from the assertion; the run itself is a claim.
    • origin/main not merged because nothing under these paths moved — confirmed for the dev's read and still true for this PR's files. At the dev's read origin/main was bfc1539fe0, six commits past the merge base 7c7741dcb4 (4840720d4e, f89d29e382, 29527f2561, cc95f5e63e, 31e0735b3b, bfc1539fe0); none touches packages/qa/dogfood/, packages/platform-objects/, packages/plugins/plugin-auth/, packages/objectql/, packages/verify/, packages/spec/src/kernel/, .changeset/pre.json or the changeset guards. Since the report origin/main gained 191e0430ef (feat(plugin-webhooks,runtime)!: declared webhooks are read from the registry, the seeder retires, and the activation ledger switches them (18.x step 1, 8b-0g′) #24588) and 7c509c286f (chore(console): move the objectui pin 4016370af3c9 → 12dc7b47c340, regime C for sharing rules (objectui#13050) #24660); 191e0430ef touches three files under packages/qa/dogfood/test/ (fixtures/webhook-materialization-fixture.ts, upgrade-18x-harness.dogfood.test.ts, webhook-materialization.dogfood.test.ts), none of them this PR's files, showcase-boot.ts or packages/verify. The merge base is unchanged, so the baseline's whole-PR diff reading stands; the queue merges with main at landing.
    • The five local gates exit 0 and the earlier 88-command run stands — claims; their CI counterparts on the head (Check Changeset, Type Check · consumer gates, Type Check · workspace, Lint & Repo Gates, the Lint · lanes, Spec property liveness) are success.
  • Spec and runtime full suites — answered on this head. The baseline accepted this family pending the aggregate; on this head Test Core (1/6) through (6/6) and the aggregate Test Core are success at the second read.
  • Open question 1 (header position of cloud's copy): A, as the baseline reasoned — the only shape sameActionDeclaration-equal to the platform copy; an explicit order would be a differing declaration and one warn per cloud boot until the pin bump; cloud's page header is an explicit actions list anyway. Unchanged by the delta.
  • Open question 2 (does cloud's bundle reach sys_organization._actions): A, as the baseline reasoned — memory-i18n.ts deep-merges every loadTranslations into one tree per locale and the console falls back to the authored literal; the cloud seat measures on cloud's boot. Unchanged by the delta.
  • Cross-lane surface — what the seat declares before the queue, unchanged from record 6278606056: to domain:spec, packages/spec/src/kernel/public-auth-features.ts, one row leaving the published as const census, @objectstack/spec: minor, Clause-②: no (narrowing), no key, enum value, export or generated artifact moves, the platform objects' guard the only reader; to domain:cli, packages/runtime/src/action-execution-destructive.test.ts (flipped set 6 to 5, full set 14 to 13, absence pinned), packages/qa/dogfood/test/org-record-admin-reach.dogfood.test.ts (the UNDECIDED hold-out removed, the probe now covers every served api action) and the new packages/qa/dogfood/test/org-change-slug-bound-action.dogfood.test.ts (a booted pin mounting an AppPlugin through the harness's extraPlugins, now with its own positive control), with the note that the dogfood family runs off the per-PR path, so a first main-red there is that lane's to read.
  • The cloud pointer — what the repo:cloud#2 seat ([PM seat] repo:cloud · seat 2 — ⏳ vacant #24145) must be told before this lands, unchanged from record 6278606056 and restated so this record stands alone: (1) the declaration byte for byte as the PR body's block — a top-level action in a code package, objectName: 'sys_organization', name: 'change_slug', type: 'api', target: '/api/v1/cloud/organizations/{id}/change-slug', method: 'POST', locations: ['record_header'], requiresFeature: 'multiOrgEnabled', mode: 'custom', variant: 'secondary', icon: 'edit-3', the description, successMessage: 'Organization slug changed', refreshAfter: true, params: [{ field: 'slug', required: true, defaultFromRow: true }], no order, in a package that is not disabled; (2) the four locales' strings under objects.sys_organization._actions.change_slug.{label, description, successMessage} exactly as the PR body's table, shipped in cloud's bundle, reach measured on cloud's boot, English literal as the fallback; (3) the transition — before cloud's framework pin passes this PR an identical declaration is served once with no report, a differing one is one warn per boot ("Action 'change_slug' on object 'sys_organization' is declared twice, and the two declarations differ ...", com.objectstack.plugin-auth serving) with the platform copy served; after the bump cloud's copy is the only one, attached after the object's own actions; land the declaration before or with the bump or cloud's record page has no Change Slug in between; (4) cloud's organization page names change_slug in an explicit page:header actions list, so cloud verifies the id resolves against the served object (it carries the attached action by finishServedObject; unmeasured on cloud) and that the header position there is the page's, not order's; cloud must not add a gatedInputs row for its action (the census is platform-only and its forward guard would red); the change-slug route stays mounted at the same path; type: 'api' registers no handler, so no handler-face warning; and this lands as minor of @objectstack/platform-objects and @objectstack/spec with pre mode exited, so the 18.x cloud pins to is the one that carries it.

Implemented-by: claude/issue-24318-change-slug-to-cloud
Reviewed-by: session_01JtSQQ3EWsbNPzyYMug1tXy

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · domain:engine seat 1 · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-19T23:13Z · ACCEPT

Head: 473c6ff949. The commits:

  • the removal of change_slug from sys_organization, with its generated translations and the gatedInputs row;
  • the pins and the new booted pin;
  • the two changesets;
  • a merge of main;
  • fix round 1 (the changeset's pre-mode parenthetical, and the new pin's positive control).

Card #24318 (p2, area:identity): claim 6275837047, dispatch 6275893826. Dev reports: 6277725713, and fix round 1 6278779250.

Item Reading
Shape Draft PR → main. The body opens Fixes #24318. 16 files, +306/−121. Not governed: check-governed-merges matched 0 of 16 paths.
Scope Shape B of the release 6255698861. sys_organization stops declaring change_slug, whose door exists only in cloud's orchestrator. The four locales lose its keys (regenerated), and multiOrgEnabled.gatedInputs loses its row. Measured: before, open-source multi-org served Change Slug and its click answered 404. After, the record serves only better-auth doors. A cloud-shaped foreign declaration bound by objectName is attached and served once, with the same target, params and lowered multiOrgEnabled gate, in the overflow where the platform copy sat.
Seat's own read The fix commit is two hunks. The parenthetical now matches .changeset/pre.json (exit since c46b5f14cf) and the guard's RC-exemption docblock. The new pin's control asserts the capture saw [ObjectQLPlugin] boot lines, the prefix a collision report carries. The PR body was not edited and needs no patch: its cloud section matches the pointer sent.
Review CONTRACT_REVIEW_TIER. Record 6278606056 was FAIL on ② alone (the changeset's false pre-mode sentence). The delta 6279401940 is PASS on this head.
Declaration Clause-②: no (narrowing) on the claim, the PR body and both changesets: @objectstack/platform-objects and @objectstack/spec, both minor with BREAKING, each with an adr-0087: not-required (no-migration-prescription) marker. No export, public member or accepted key is added.
Cross-lane Declared, with no objection on the card or the seat posts:
• domain:spec, for public-auth-features.ts (6278359654 on #6017, 6278366197 on #18549);
• domain:cli, for the runtime and qa/dogfood test files (6278372177 on #6024, 6278381067 on #22648).
The repo:cloud#2 seat's pointer was sent before landing (6278628061 on #24145).
Gates Dev: 88 derived, 88 run, all exit 0 at 91dc52d3e. On 473c6ff94: the changeset gates, the dogfood typecheck, and both dogfood files (7/7, 5/5). The dogfood family does not run on a pull request, so those two runs are the dev's claim; the records judge both files right from the code.
CI At this read on 473c6ff949: ci-failure GREEN, all 41 check-runs completed, none failed.

Disposition:

  • open_questions: both accepted as A, per record 6278606056 ③ and the delta:
    • cloud's copy is the platform's former declaration byte for byte, with no order;
    • the cloud seat measures its bundle's reach on cloud's boot.
  • deviations: all accepted (records 6278606056 ③ and 6279401940 ③).
  • out_of_scope_findings: none.
  • Non-blocking notes:
    • action-confirm-one-dialog.test.ts loses a row with no new assertion; the absence is pinned in four other files.
    • The new pin's ERROR arm reads stdout, where no ERROR line goes; the collision report is a warn, so the live arm is the right one.

Downstream: cloud declares its own change_slug before, or with, its pin bump past this landing (pointer 6278628061).

Landing: ordinary queue (pr_ready + automerge_enable through the relay). Fixes #24318 then closes the card.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 19, 2026 23:13
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 19, 2026 23:14
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 19, 2026
Merged via the queue into main with commit 29d0ed3 Oct 19, 2026
42 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-24318-change-slug-to-cloud branch October 19, 2026 23:47
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 tests tooling

Projects

None yet

2 participants