Skip to content

feat(platform-objects): sys_organization declares a record_header invite action; leave_organization folds to the overflow and names what is lost - #24216

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-24016-org-invite-action
Oct 17, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-24016-org-invite-action

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #24016

#24021's copy half rides here (ruling 6228318638 item 2); that card's record-scope key is not added.

Clause-②: no

What

sys_organization declares the invite action ruling 6228318638 (letter B′ on #24034, item 1) names, so an organization record page's page:header can list it. A page:header resolves action ids against its own object only.

  • invite_user at record_header: type: 'api', target: '/api/v1/auth/organization/invite-member', recordIdParam: 'organizationId', requiresFeature: 'organization', requiresMembershipReach: 'invite_member', variant: 'primary', order: -1. Its params are email and role, both required, with the role picker on INVITATION_ROLE_OPTIONS, as sys_invitation.invite_user declares them. Its description and success message match the other three mirrors (sys_user, sys_invitation, sys_member).
  • leave_organization keeps variant: 'danger' and gains order: 1. Its confirmText now names what is lost: "Leave this organization? You will lose access to its environments and members." The zh-CN, ja-JP and es-ES bundles carry translated text.
  • No new spec key.

Measurements behind the choices

  • H1, the params. email and role are not fields of sys_organization. A field-backed param resolves against the action's own object unless objectOverride names another. So both params carry objectOverride: 'sys_invitation', the device sys_member's mirror already uses for email. Apart from that the shape is sys_invitation's (required, same options). The existing "every field-backed param resolves to a real field" pin now covers all four mirrors.
  • H2, the gates. Both keys are declared on ActionSchema (requiresFeature, requiresMembershipReach in packages/spec/src/ui/action.zod.ts) and are already used by all three mirrors. The lowered visible matches theirs byte for byte; that is pinned.
  • H3, the order. I read objectui 9753811's page:header renderer (packages/components/src/renderers/layout/containers.tsx). It stable-sorts by order (unset = 0) and promotes the first non-destructive variant: 'primary' action. Then the first maxVisible authored actions (default 3) go inline and the rest fold into the ⋯, with destructive ones in its last band. With only the invite added, the row was invite, set_active, leave, and change_slug folded. So leave_organization stayed inline as the third button. For it to "fall to the overflow" it needs a declared position: order: 1 sorts it last, so the inline row is invite, set_active, change_slug and leave folds. This reuses the spec's existing order, so no new key.
  • H4/H5, the bundles. en.objects.generated.ts is rewritten from source on every extract. The translated locales keep their values in merge mode, so the new leaves were seeded in English by pnpm i18n:extract. I set them to the sibling sys_invitation.invite_user translations and translated the new leave_organization.confirmText, then re-ran the extract. It is idempotent and drops the seeded source-hash entries. pnpm check:i18n: all 10 packages in sync.

Outside the claim's file surface

packages/spec/src/kernel/public-auth-features.ts gains one row: sys_organization.actions.invite_user in PUBLIC_AUTH_FEATURES.organization.gatedInputs. This is a ledger entry, not a key. platform-objects/src/feature-gate-guard.test.ts requires every features.* gate in the objects to be booked there, and it went red without the row. The ruled requiresFeature: 'organization' cannot land without it. pnpm --filter @objectstack/spec check:generated reports all 14 artifacts up to date, and the changeset adds a @objectstack/spec patch.

Tests

  • New sys-organization-invite-action.test.ts pins:
    • the record-header placement: location, endpoint, recordIdParam;
    • the invite is the only primary action and sorts first;
    • leave_organization stays danger and sorts past the inline budget.
  • invite-entry-toolbar.test.ts and invite-user-action-copy.test.ts now include sys_organization as a fourth mirror for endpoint, gates, inputs, param resolvability, copy, translated bundles and served metadata. The list-toolbar location pin stays on the three toolbar mirrors.
  • Ablation: deleting leave_organization's order: 1 through scripts/ablation-replace.mjs landed (anchor 1 to 0, blob 919503b9 to 70b55e52). The test "sorts last, past the inline budget once the invite leads" went red (1 failed, 4 passed). The file was restored to the HEAD blob and git diff HEAD was empty.
  • pnpm --filter @objectstack/platform-objects test: 74 files, 1162 tests passed. typecheck: exit 0. src/kernel/public-auth-features.test.ts: 24 passed. All on 9aa48a7e5, after merging origin/main, which brought feat(cloud-connection): "Installed Apps" opens Setup Packages filtered to Source = marketplace #24200 and feat(plugin-audit): Setup has one audit entry, "Audit Log", and it opens the audit browser #24204; the bundles regenerated byte-identical.
  • Local gates on 9aa48a7e5: node scripts/pm/dispatch-gates.mjs --commands derives 86 commands. All 86 exited 0, and --ran reports "86 run, 0 NOT-MEASURED (a DERIVED zero)".

Acceptance notes

  • leave_organization folds only when the header has more than 3 visible authored actions. That is the case for an admin in multi-org: invite, set_active, change_slug, leave. A plain member does not see the invite, so leave stays inline as a red outline button. It is never the primary (objectui#12764). Moving it to the overflow unconditionally would take component: 'action:menu'. I did not do that: the ruling speaks in terms of order, and cloud#2910 overrides order and variant per its own posture (ruling item 5).
  • The invite is gated on the organization capability, as the ruling says, while its record-header siblings use multiOrgEnabled. In single-org mode the invite is the only header action.

Generated by Claude Code

…ite action; leave_organization folds to the overflow

Claude-Session: https://claude.ai/code/session_01Eey1448sFJ5Z95gbXZh2tG
Co-authored-by: Claude <noreply@anthropic.com>
…er placement, add changeset

Claude-Session: https://claude.ai/code/session_01Eey1448sFJ5Z95gbXZh2tG
Co-authored-by: Claude <noreply@anthropic.com>
…ization feature gate

Claude-Session: https://claude.ai/code/session_01Eey1448sFJ5Z95gbXZh2tG
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 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

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

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

Coarse fallback — 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 8259ea03e2959129c688639f69d0782c3a820f89 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 8259ea03e2959129c688639f69d0782c3a820f89

⚠️ 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 8259ea03e2959129c688639f69d0782c3a820f89 → 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: 9aa48a7e5a1bcd99e1d200d1ee13ef004dee6908
Local-runs: none
Scope: packages/spec/src/kernel/public-auth-features.ts + packages/platform-objects/src/feature-gate-guard.test.ts, packages/platform-objects/src/identity/sys-organization.object.ts, packages/spec/src/kernel/public-auth-features.test.ts, packages/plugins/plugin-auth/src/public-feature-registry.test.ts, packages/spec/src/ui/action.zod.ts, packages/platform-objects/src/identity/invite-entry-toolbar.test.ts, packages/platform-objects/src/identity/sys-organization-invite-action.test.ts, .changeset/24016-org-invite-action.md (read for ②). The remainder of the diff (the four translation bundles and invite-user-action-copy.test.ts) is the lane seat's ACCEPT at TIER_DEFAULT, exactly as a standalone PR of those files.

Inputs: card #24016 (body and all six comments, including claims 6238369945 and 6240102228 and os-dev-report 6239623631), ruling 6228318638 on #24034 (letter B′), PR #24216 body, file list and the net diff against main at the head (merge-base bb7ccde), and the 40 check-runs on the head. Not read: the dispatch order or the dispatching seat's conclusions.

Check-runs on the head at 2026-10-17T20:14Z: 33 success, 6 skipped (paths-filtered lanes: Dogfood Verify CLI, Dogfood Regression Gate shards, Console Pin Gate, Build Docs, Temporal Conformance, Packed-tarball smoke), 1 still in progress (Test Core 1/6), 0 failure. Green at read time: TypeScript Type Check (the lane that runs check:generated, check:api-surface and the spec artifact gates), Check Changeset, Governed Surface Queue Guard, Spec property liveness, Lint and Repo Gates (all nine Lint lanes, check:i18n among them), Build Core, Test Core 2 through 6, and the three card-claim guards. The in-progress shard is recorded as such; this record does not wait on it, and the landing pre-check that every check is green stays the adopting seat's to confirm once it completes.

① Derived judgments

  1. PUBLIC_AUTH_FEATURES.organization.gatedInputs gains the string sys_organization.actions.invite_user (4 lines, one comment plus the row). Accept set: unchanged, judged RIGHT. PUBLIC_AUTH_FEATURE_NAMES (the key set that feeds the requiresFeature enum on ActionSchema and ParamSchema at action.zod.ts lines 453 and 1493) is untouched; no *.zod.ts file, no authorable key and no describe text changes; the spec artifact lane (TypeScript Type Check) is green on the head, which is the check:generated verdict.
  2. Public surface: PUBLIC_AUTH_FEATURES is an exported const on the @objectstack/spec/kernel entry; api-surface/kernel.json records its existence only, and that snapshot is unchanged. The value's inferred const tuple gains one literal. Every consumer of the element set in the tree is a guard that reads it as data: feature-gate-guard.test.ts (both legs), public-auth-features.test.ts (grammar and XOR pins), the checklist anchor at docs/qa/platform-checklist/areas/identity-auth.json which names the organization member and not its rows, and plugin-auth's public-feature-registry.test.ts which reads keys and semantics only. No consumer narrows or widens behaviour on the row. Judged RIGHT: bookkeeping, not a published-surface expansion. Precedent: the third mirror (sys_member.actions.invite_user, console: org record page's default Members tab hides the email-invite entry — invite_user lives only on the Invitations tab #11544) landed the same one-row booking and shipped in 17.3.0 under Patch Changes in both packages with the sentence "No schema, export or authorable key changes".
  3. The row is entailed by the ruling, not added beside it. Ruling item 1 fixes requiresFeature: 'organization'; lowerRequiresFeature turns that into features.organization != false in the action's visible; the reverse leg of feature-gate-guard.test.ts reds any features.* reference in a platform object's visible that is not booked in that flag's gatedInputs. So the ruled gate cannot land without the row. The ruling's binding line is "no new spec key" and it holds; its "no spec change" sits in the workload estimate, which the guard falsifies. Judged RIGHT, and the file-surface widening was re-declared on the card (claim 6240102228, cross-lane to domain:spec) before this review.
  4. Row placement under organization and not multiOrgEnabled: RIGHT. ADR-0133 states the gate is the organization capability, never multi-org, and that member-management inputs sit under organization while organization lifecycle actions sit under multiOrgEnabled; ADR-0093 D8 item 2 records the dual-flag distinction as deliberate. The new row joins the three other invite_user rows in the same entry.
  5. Grammar pin: the path matches the object.actions.action form public-auth-features.test.ts requires. The reverse-walker floor (at least 37 referenced inputs) can only rise by one. RIGHT.
  6. Forward leg on the cited action: sys_organization.invite_user declares requiresMembershipReach: 'invite_member' and requiresFeature: 'organization'; the lowered visible composes the grade predicate first and the feature gate last, and invite-entry-toolbar.test.ts now pins that source byte for byte over all four mirrors, which is exactly the shape the forward leg's "equals or ends with the gate" check requires. RIGHT.
  7. The action's shape against ruling item 1, read in sys-organization.object.ts at the head: type: 'api', target: '/api/v1/auth/organization/invite-member', recordIdParam: 'organizationId', requiresFeature: 'organization', requiresMembershipReach: 'invite_member', variant: 'primary', order: -1, locations: ['record_header']. Every key is declared on ActionSchema (objectOverride line 197, locations line 1002 with record_header in the enum, variant line 1311, order line 1332, requiresMembershipReach line 1503, recordIdParam line 1566). The MEMBERSHIP_REACH row for invite_member admits owner, admin and delegated_admin, matching the PR body and changeset. RIGHT, item 1 verbatim.
  8. Params email and role: both required, role on INVITATION_ROLE_OPTIONS (exported from spec identity/invitation-role-grant.ts via the identity entry), which is sys_invitation's declared shape. Both carry objectOverride: 'sys_invitation' because neither is a field of sys_organization; sys_invitation declares both fields (email at its line 263, role at 275); sys_member's mirror already uses the same device for email; the existing resolvability pin now runs over all four mirrors. RIGHT, and the only honest way to satisfy "as sys_invitation.invite_user declares them" on an object that lacks the fields.
  9. leave_organization keeps variant: 'danger' and gains order: 1 using the existing order key, whose own describe text is "Promotes/demotes an action toward the record_header primary button; stable". The header set at the head is set_active_organization (order unset), invite_user (-1), leave_organization (1), change_slug (unset); a stable sort gives invite, set_active, change_slug, leave: four actions against the renderer's default inline budget of three, so leave folds. The new test pins that ordering from the declared metadata alone. RIGHT, within item 1's "falls to the overflow", declared on the card (claim 6240102228), and no new key.
  10. confirmText on leave_organization now names the organisation's environments and members, in en and the three translated bundles: ruling item 2, with no record-scope key added. The copy is RIGHT; the bundle regeneration itself is the lane seat's remainder and check:i18n is green on the head.
  11. Served metadata: the sys_organization object served over the metadata API gains one action entry. That is additive data, not a new key on a payload schema, and it is the same shape as the 17.3.0 precedent. Not a narrowing: nothing is removed; the replaced confirmText is copy. RIGHT.
  12. Nothing in the diff touches a governed path (Governed Surface Queue Guard green; no .claude/**, docs/adr/**, skills/**, workflow or AGENTS.md path in the file list). The PR is Tier S by this face alone, owed by packages/spec/src/** non-test content.

② Semver level

Changeset .changeset/24016-org-invite-action.md: @objectstack/platform-objects: patch, @objectstack/spec: patch. The PR body and the changeset body both carry Clause-②: no at the start of a line, with no direction arm. Clause ② asks whether the card widens a published accept set or expands the public surface (a new key on a published payload). It does neither: no key is added to any published payload, the requiresFeature enum is unchanged, the spec change is one registry row that only test guards consume, and the platform-objects change is additive metadata on an existing object. It is not a narrowing either. no with patch is the shape AGENTS.md's level rule allows (yes would force at least minor; no (narrowing) would be breaking), it matches the identical-shape precedent that shipped as patch in both packages at 17.3.0, and the Check Changeset gate (the level axis of check-changeset-no-major.mjs) concluded success on the head. The changeset text is accurate on every claim I could read in the code: endpoint, organizationId, both params, the grade-capped role picker (ADR-0105 D8), the owner/admin/delegated_admin grades, order -1, leave's danger variant and order 1, the four-locale confirmText, and the spec booking. Judged RIGHT.

Clause-②: no

③ Boundary flags

Dev deviations (os-dev-report 6239623631):

  1. File-surface breach, one spec registry row: ANSWERED, accepted. Gate-entailed by the ruled requiresFeature: 'organization' (① item 3), not a new key, re-declared on the card as cross-lane to domain:spec (claim 6240102228), and this record is the CONTRACT_REVIEW_TIER review that re-claim says the row owes.
  2. Third edit on sys_organization, order: 1 on leave_organization: ANSWERED, accepted. An existing key whose describe text names this exact use; measured necessary for the ruling's "falls to the overflow"; declared on the card.
  3. objectOverride: 'sys_invitation' on both params: ANSWERED, accepted (① item 8).
  4. Hand-written translated-locale values: ANSWERED, outside the contract faces. AGENTS.md's Documentation Guardrails row permits exactly this and check:i18n is green; the lane seat's ACCEPT covers the bundles.
  5. Local branch housekeeping and 6. commit trailer choice: process notes with no contract bearing; nothing to answer here.

open_questions:

  1. Fold leave_organization unconditionally via component: 'action:menu' (B) or keep order: 1 (A): ANSWERED A. order is the ruling's own vocabulary; item 5 has cloud#2910 override order and variant for its own posture; B adds a second placement lever nobody ruled for. For a plain member (no invite, header at or under three actions) leave stays an outlined danger button and is never the primary, which is what the ruling forbids. No escalation.
  2. Gate the invite on organization while its header siblings use multiOrgEnabled: ANSWERED A, and not a judgment call: ADR-0133 ("the gate is the organization capability, never multi-org") and ADR-0093 D8 item 2 record the split as deliberate; in single-org mode the invite being the only header action is the open-member-management posture those records describe. No escalation.

Reviewer notes for the adopting seat (not blockers):

  • ADR-0133's descriptive prose says invite_user "is declared on three objects" and names three copies; this PR makes four. The ADR states it records no new decision, and both of its binding statements (invitation-only adds; organization-capability gate) are honoured by the fourth mirror; both symbols it anchors in invite-entry-toolbar.test.ts (INVITE_ENDPOINT, OBJECTS_BY_NAME) survive. The count is now stale prose on a Tier H surface and is not this PR's to edit; an acceptance note or a docs-only follow-up is the right carrier.
  • Test Core shard 1/6 was still in progress at read time; the queue's all-green requirement is separate from this verdict.

Implemented-by: claude/issue-24016-org-invite-action
Reviewed-by: session_01JtSQQ3EWsbNPzyYMug1tXy

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

CI red · domain:engine seat 1 · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-17T20:31Z · head 9aa48a7e5a

Failing check: Test Core, the aggregate (run 39062747591). It is red because Test Core (1/6) was cancelled at the job wall: annotation "The job has exceeded the maximum execution time of 50m0s". The shard spent about 43 minutes in "Run this shard's tests". No assertion failure is on record, and the stall guard uploaded no reports.

Signature (new; no open card matches): shard 1/6 times out at the 50-minute wall in its test step on a pull_request run. The shard's job log answers 404 through the API, so the stalled file is NOT MEASURED.

Initial judgment: not shown to be this PR's.

Act: one re-run of the failed jobs on this head, which also yields a readable shard-1 log. If shard 1/6 times out again on this head, the signature is real. It is then diagnosed from that log on this PR, and filed if it is not this diff's.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · domain:engine seat 1 · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-17T20:59Z · ACCEPT

Head: 9aa48a7e5a.

Item Reading
Shape Draft PR → main. The body opens Fixes #24016, its only closing keyword. #24021's copy half rides here, as ruling 6228318638 item 2 says.
Scope 10 files (+276 / −34). That is the re-declared claim's surface (6240102228): sys-organization.object.ts (the invite action, leave_organization's order: 1 and its confirmText), the four translation bundles, three pins, the changeset, and the one cross-lane spec row declared on #6017. Not governed (check-governed-merges.mjs --pr 24216: ordinary queue, 310 lines).
Ruling Ruling 6228318638 (B′ on #24034), item 1 read key by key at the head: type: 'api', the invite-member target, recordIdParam: 'organizationId', requiresFeature: 'organization', requiresMembershipReach: 'invite_member', variant: 'primary', order: -1, locations: ['record_header']. Every key is declared on ActionSchema. Item 2: the confirm copy names the environments and members in en, zh-CN, ja-JP and es-ES. No new spec key.
The spec row Entailed by the ruled requiresFeature: 'organization': the reverse leg of feature-gate-guard.test.ts reds any features.* gate not booked in gatedInputs. The seat re-read the guard. No non-test code reads gatedInputs (git grep at the head finds only the two typecheck-debt ledgers). The same one-row booking shipped as a patch in both packages in 17.3.0 ("No schema, export or authorable key changes", both CHANGELOGs). The ruling's workload line "no spec change" was an estimate; its binding line "no new spec key" holds.
H3 (the order) Confirmed by the dev, and the seat re-read it. objectui origin/main's action-bar.tsx sorts by order ascending (unset = 0), then primary, then registration order, and its default maxVisible is 3 (page-header-actions.test.tsx, "overflows past the default maxVisible of 3"). The head's four record_header actions are set_active_organization, invite_user, leave_organization and change_slug. So an admin in multi-org sees invite, set_active and change_slug inline, and leave folds.
Review face Published schema (packages/spec/src/kernel/public-auth-features.ts). The CONTRACT_REVIEW_TIER record 6240346929 on this head is PASS. The seat verified its central claims: ADR-0133's "the gate is the organization capability, never multi-org"; the 17.3.0 precedent; no non-test reader of the row; the header set above. The remainder (the bundles and invite-user-action-copy.test.ts) is this ACCEPT at TIER_DEFAULT.
Prose face Changeset, sentence by sentence. The endpoint, organizationId, both params, the grade-capped role picker, the owner / admin / delegated_admin grades, order: -1, leave's danger and order: 1, the four-locale copy and the spec booking each match the diff. "folds into the ⋯ overflow menu instead of taking the third inline slot" holds where the invite leads a full header; the PR's Acceptance notes state the condition (a plain member keeps leave inline, never as the primary).
Bundles en is generator output; the translated-locale values are hand-written, which AGENTS.md's Documentation Guardrails row allows ("Translated-locale values … are hand-written: editing one of those strings is fine"). check:i18n is green on the head. The zh-CN strings read correctly.
Pins and ablation The new sys-organization-invite-action.test.ts pins placement, the single primary, the sort, and leave past the inline budget. The three mirrors' pins now run over four. One ablation (delete leave's order: 1) turns "sorts last, past the inline budget once the invite leads" red, 1 of 5. The restore is clean (blob back to 919503b9).
Clause-② no, at a line start in the PR body and the changeset; patch for @objectstack/platform-objects and @objectstack/spec. Check Changeset is green.
Gates Dev union: 86 derived, 86 run, 0 NOT MEASURED. CI on this head at this read: 41 check-runs, 35 success, 6 skipped (each on the EXPECTED_SKIPS roster in scripts/pm/check-expected-skips.mjs), 0 failed. Test Core (1/6) hit the 50-minute job wall on attempt 1 with no assertion and an unreadable log (signature and judgment: 6240560569); the one re-run passed it in 22m54s, and the aggregate Test Core is green.

Disposition:

Landing: ordinary queue (pr_ready + automerge_enable through the relay) once every check on this head is green.


Generated by Claude Code

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