Skip to content

feat(platform-objects): Add Member opened from a team binds that team - #25670

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-25337-last-team-delete
Oct 24, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-25337-last-team-delete

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

Fixes #25337

Clause-②: no
Metadata-protocol: none

What changes

In Setup → Teams, Add Member lived only on the team-membership list's toolbar, where no team is in hand, so its dialog asked the administrator to pick the team a second time. sys_team now declares its own add_team_member action, on the team row (list_item) and the team record header (record_header):

  • It calls the same POST /api/v1/auth/organization/add-team-member endpoint as the membership-list copy, behind the same gates (requiresFeature: 'organization' and requiresMembershipReach: 'add_team_member').
  • The team's id rides as teamId from the row the action is opened on (recordIdParam: 'teamId'), so the dialog asks only for the user (userId, through the membership row's sys_user lookup via objectOverride: 'sys_team_member').
  • The membership-list copy (sys_team_member.add_team_member) is unchanged and still asks for both.
  • Translations for the new action label and success message in all four bundles; platform-objects patch changeset.

This is the add-member half of the card only. Under ruling C the delete half is not done: no disabled-reason key, no computed last-team fact, and allowRemovingAllTeams stays unset.

Outside the claim's file surface

packages/spec/src/kernel/public-auth-features.ts gains one row, 'sys_team.actions.add_team_member', in PUBLIC_AUTH_FEATURES.organization.gatedInputs, immediately after 'sys_team.actions.remove_team', with an @objectstack/spec patch changeset (.changeset/25337-spec-book-team-add-member.md). The existing 'sys_team_member.actions.add_team_member' row (the membership-list action) is untouched.

This sits in the domain:spec lane and lands here under ruling 6370058183 (A + C) and its confirmation 6384716955: feature-gate-guard.test.ts checks the booking in both directions, so the row can only land in the same PR as the action. A declaration is posted on spec seat post #6017. The spec face goes through the CONTRACT_REVIEW_TIER review before the queue.

Serial note: open PR #25626 and the second PR under #25347 each move a different row of the same file. Neither had landed when this PR opened (origin/main at 2c523d010; the only commit since this branch's merge of main touches .claude/skills/pm-dispatch/references/** and scripts/pm/check-half-states.mjs). Whichever lands later merges main and re-runs feature-gate-guard.test.ts.

Tests

All readings below are on head 67affbcd1.

  • pnpm --filter @objectstack/platform-objects exec vitest run --maxWorkers=2 src/feature-gate-guard.test.ts src/identity/sys-team-add-member-action.test.ts src/platform-objects.test.ts → 3 files, 259 tests passed (after pnpm --filter '@objectstack/platform-objects^...' build, so the guard reads the rebuilt spec dist/).
  • pnpm --filter @objectstack/platform-objects test → 95 files, 1351 tests passed.
  • pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/kernel/public-auth-features.test.ts src/ui/action.test.ts → 2 files, 177 tests passed.
  • pnpm --filter @objectstack/platform-objects --filter @objectstack/spec run typecheck → exit 0.
  • ESLint (--no-inline-config) on the eight changed source files → exit 0.

Ablation: the guard turns red without the row

The guard reads @objectstack/spec/kernel through exports (the built dist/), so each leg rebuilt spec and proved the marker's state in dist/ before the run was read.

  • Mutate: scripts/ablation-replace.mjs deleted the row (anchor 1 → 0, blob bc905d9641b5 → 5b8ba535bc57); pnpm --filter @objectstack/spec build; scripts/ablation-dist-preflight.mjs @objectstack/spec 'sys_team.actions.add_team_member' --absent → marker absent from all 232 built files. Guard run → 1 failed | 102 passed (103):
    FAIL  src/feature-gate-guard.test.ts > feature-gate completeness guard (#2874) > reverse: every features.* gate in the objects is booked in the registry > sys_team.actions.add_team_member references registered flag organization and is booked
    AssertionError: sys_team.actions.add_team_member is booked in organization.gatedInputs: expected false to be true
    
  • Restore: blob back to bc905d9641b5 (equal to HEAD), git diff HEAD empty, whole-tree git status --porcelain clean; spec rebuilt; preflight (present) → marker in 30 built files, tree clean. Guard run → 104 passed (104) (the booked row also adds its forward-direction case).

Gates

node scripts/pm/dispatch-gates.mjs --commands derived 91 commands for this change set; every one was run on 67affbcd1 and recorded as cmd :: exit N. All 91 exit 0. Four first answered exit 3 (PREREQUISITE NOT MET: check:i18n, check:lean-entry-closure and check:dual-build-cjs-loads needed built packages, and the check-plugin-teardown-shape.mjs --self-test fixture commit was outside the shallow clone). Each was re-run after the named prerequisite was met (the i18n closure build; fetching the pinned fixture commit) and exited 0. node scripts/pm/dispatch-gates.mjs --ran → "91 derived famil(ies) accounted for — 91 run, 0 NOT-MEASURED". Local-only node scripts/check-dev-prereqs.mjs → exit 0. CI owns the type-check lanes, Test Core, Build Core and the roster families outside the derived set.

Acceptance notes

  • No new runtime path: the action is metadata only, and it reuses an endpoint and gates that are already served.
  • packages/plugins/plugin-auth/src/public-feature-registry.test.ts also imports PUBLIC_AUTH_FEATURES, but it reads flag names and semantics only, never gatedInputs. It was not run locally (its dependency closure is unbuilt here); CI's Test Core runs it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JrXUZYyTV4i588KLZRxSvJ

@objectstack-fleet
objectstack-fleet Bot requested a review from os-sales October 24, 2026 14:17
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

38 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 2c523d0100e9368ba8af3b08c652f23e943de46a.

⛔ 8 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 — 144 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 2c523d0100e9368ba8af3b08c652f23e943de46a → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 2c523d0100e9368ba8af3b08c652f23e943de46a

⚠️ 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 2c523d0100e9368ba8af3b08c652f23e943de46a → 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: 67affbcd18a6bab65645e73a5c707e1bc171869f
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-team.object.ts, packages/platform-objects/src/identity/sys-team-member.object.ts, packages/platform-objects/src/identity/sys-team-add-member-action.test.ts, packages/platform-objects/src/platform-objects.test.ts, packages/spec/src/kernel/public-auth-features.test.ts, packages/plugins/plugin-auth/src/public-feature-registry.test.ts, .changeset/25337-spec-book-team-add-member.md

PR #25670 (draft, author objectstack-fleet[bot]) at the head above, read against origin/main at 2c523d0 (the PR's base; merge base baae6ed). Face: published schema (a non-test file under packages/spec/src), read off the committed file list. Inputs: card #25337 with all eight of its comments, the PR body and its one comment, the file list, the net diff (10 files, 137 insertions, 0 deletions) and the head's check-runs. The rest of the diff (the sys_team action, its test, the four generated bundles, the lowering-matrix row and the platform-objects changeset) is the lane seat's ACCEPT at TIER_DEFAULT, exactly as a standalone PR of those files.

① Derived judgments

  1. The one row, in the one place. PUBLIC_AUTH_FEATURES.organization.gatedInputs gains exactly 'sys_team.actions.add_team_member' at line 126, immediately after 'sys_team.actions.remove_team' (line 125) and before 'sys_team_member.actions.add_team_member' (line 127); the file's net diff is one added line and nothing removed. This is what rulings 6370058183 (「补一行 spec,删除半张不做」, A + C) and 6384716955 (「维持 旧裁决 A + C」, with the placement and the same-PR requirement) ask for. Right.
  2. The membership-list row is untouched. 'sys_team_member.actions.add_team_member' stands unchanged on line 127, and sys-team-member.object.ts is not in the diff. Right.
  3. No delete half. sys_team.remove_team is byte-identical (no disabled, no reason text), no reason key enters action.zod.ts, no registry row names a delete, and packages/plugins/plugin-auth/src/auth-manager.ts is not in the file list, so allowRemovingAllTeams stays unset. Ruling C holds. Right.
  4. What the row claims resolves, and the gate reads it in both directions. sys-team.object.ts declares add_team_member on sys_team with requiresFeature: 'organization' and requiresMembershipReach: 'add_team_member'; lowerRequiresFeature composes the gate last, and the lowering-matrix row added to platform-objects.test.ts pins the lowered visible as the grade term followed by features.organization != false as a top-level conjunct, the same shape as the three sibling sys_team rows. So feature-gate-guard.test.ts's forward leg (the registry row resolves to an action carrying the gate conjunct) and its reverse leg (every features.organization reference is booked) both hold on this head, and neither could hold with the row and the action in separate PRs, which is why both are here. The walker's floor (at least 37 referenced inputs) only rises. Right.
  5. Grammar and registry invariants. The path matches the spec test's object-dot-actions-dot-action regex and the guard's resolver regex; the flag count stays 12 (PUBLIC_AUTH_FEATURE_NAMES unchanged, so the requiresFeature enum is unchanged); organization still declares gatedInputs and no exempt; the plugin-auth drift guard (public-feature-registry.test.ts) compares served flag names and reads semantics only, never gatedInputs, so the served /api/v1/auth/config contract does not move. Right.
  6. Placement versus declaration order. The object declares add_team_member between update_team and remove_team; the registry row sits after remove_team. No reader of gatedInputs reads order (the forward leg is an it.each over rows, the reverse leg a Set, the spec test checks grammar and the gatedInputs-or-exempt exclusion), and the ruling fixes the position by name. Right.
  7. Public surface. The published constant is as const, so its literal tuple type gains one member; the declared entry type (gatedInputs?: readonly string[]), every export, every schema and every authorable key are unchanged, and gatedInputs is a census the completeness guard reads, not an accept set that admits or refuses authored metadata. Additive; nothing published narrows. Right, and it is what makes the Clause-②: no line below true.
  8. Serial neighbours. Open PR feat(plugin-auth): multi-org Create User joins an organization in the same step, defaulting to the admin's current one #25626 adds 'sys_user.actions.create_user.params.organizationId' and open PR feat(platform-objects,spec): a single-organization deployment can rename its organization #25661 moves 'sys_organization.actions.rename_organization' into the organization block of the same file; neither has merged, and origin/main is still this PR's base. Different rows, so the later landing merges main and the guard re-runs, as the PR body says; No other open PR may claim the same single-writer path is green. Not blocking.

② Semver level

  • .changeset/25337-spec-book-team-add-member.md: '@objectstack/spec': patch, Clause-②: no, Metadata-protocol: none; the PR body carries the same two lines at line start. Right. Metadata-protocol: none is true (no key, accepted value, enum member, metadata type, default or meaning that an author can write changes: requiresFeature: 'organization' was accepted before and is accepted identically after), and under the level rule none takes patch. Clause-②: no is true by its definition (no published accept set or public surface widens, and nothing narrows, so no (narrowing) arm is owed): item 7 above. Precedent agrees: the sys_organization.invite_user booking shipped as a spec patch under Clause-②: no.
  • Not skip-changeset: both @objectstack/spec and @objectstack/platform-objects publish, and each has its patch changeset. Check Changeset is green on the head.
  • The second changeset (@objectstack/platform-objects: patch, Clause-②: no, Metadata-protocol: none) is the lane seat's; read only for consistency with the spec row, it is consistent (a new action row on a platform object is shipped metadata, not a new authorable key).

③ Boundary flags

  • PR author is objectstack-fleet[bot]; nothing to note.
  • Dev flag, os-dev report 6385318010 open_questions, the commit-trailer conflict (the dispatch text asked for a model-named co-author trailer; AGENTS.md and the pre-push hook refuse a model identifier in that pair). Answered: the repository rule holds, and the head already complies. All five commits on the head carry Claude-Session: and the model-free Co-authored-by: Claude trailer at the anthropic noreply address, read off the commit trailers; AGENTS.md's own exemption treats even a harness-written model-named trailer as a reporting matter, not a deviation. Nothing to escalate for this PR; the dispatch template's wording is the dispatching seat's to fix, outside this record.
  • Dev flag, PR body "Acceptance notes": public-feature-registry.test.ts was not run locally and is declared to CI Test Core. Read in the cited code: it reads flag names and semantics, never gatedInputs, so this row cannot move it; Test Core is named below as pending.
  • Dev flag, PR body serial note: covered in ① item 8; accepted.
  • Card flags: both earlier open_questions (report 6364672577) were ruled A + C and confirmed; the diff implements A and omits the delete half per C. No open flag remains on the card.
  • Check-runs on the head, read at 2026-10-24T14:30Z: green — Build Core, Lint & Repo Gates, Lint · ESLint, Lint · CI release & engine gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · PM dispatch-gates, the three Lint · scoped lanes, Type Check · source gates, Type Check · consumer gates, Type Check · debt ledger, Spec property liveness, Governed Surface Queue Guard, Check Changeset, Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, Check Documentation Links, Auto Label, filter, and the four claim guards; skipped by path filter or opt-in — Build Docs, Console Pin Gate, Temporal Conformance, Dogfood Verify CLI, the Dogfood matrix shards, 18.x upgrade harness, Packed-tarball smoke; still pending — Test Core (1/6) through (6/6), Type Check · workspace; red — none. The verdict below stands on the diff; the landing still waits for every check to finish green, per the queue rule.
  • needs:contract-review is not on the PR (labels: documentation, size/m, tests, tooling); no label was touched by this record.

Implemented-by: claude/issue-25337-last-team-delete
Reviewed-by: session_01JrXUZYyTV4i588KLZRxSvJ

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · 2026-10-24T14:53Z

domain:server seat 1 (seat post #6367) · os-sales · session_01JrXUZYyTV4i588KLZRxSvJ · card #25337, the add-member half under rulings A + C 6370058183 / confirmation 6384716955 (claim 6384907862, premise amendment 6384938659, os-dev-report 6385318010) · reviewed head 67affbcd18 · base 2c523d01

  • Shape: sys_team declares its own add_team_member (list_item, record_header) on the same endpoint and gates as the membership-list copy (requiresFeature: 'organization', requiresMembershipReach: 'add_team_member'); the team id rides as teamId from the row (recordIdParam), so the dialog asks only for userId (the sys_user lookup via objectOverride: 'sys_team_member'). The membership-list copy is untouched. Four bundles, lowering-matrix row, platform-objects patch changeset. 10 files, +137 / −0. Ruling C holds: nothing on the delete half.
  • Contract review (spec face): CONTRACT_REVIEW_TIER record 6385421433 on this head, PASS. The one row lands in organization.gatedInputs after 'sys_team.actions.remove_team', as the ruling places it. Declaration on spec seat post [PM seat] domain:spec — 🟢 os-tesla · session_013odhA26dvzy7pQmevdTTvy #6017: 6385300554.
  • Seat verification on this head: after pnpm --filter '@objectstack/platform-objects^...' build, feature-gate-guard, sys-team-add-member-action and platform-objects tests: 3 files, 259 passed. Ablated (the recordIdParam: 'teamId' lines deleted from sys-team.object.ts): 1 failed / 258 passed, the failure being "binds the team from the row it is opened on, as teamId". Restored, tree clean. The dev's spec-row ablation (guard 1 failed / 102 passed, restored 104 passed) is taken as reported.
  • Seat read of the rest (TIER_DEFAULT): metadata only, no new runtime path; the endpoint and both gates are already served. Changesets: @objectstack/spec and @objectstack/platform-objects patch, Clause-②: no, Metadata-protocol: none; no !, dogfood battery not owed. Gates 91/91 exit 0 as reported.
  • Check-runs on the head: 42 runs, 35 success and 7 expected skips, none failed or pending.
  • Serial: public-auth-features.ts is also edited by feat(platform-objects,spec): a single-organization deployment can rename its organization #25661 (One organization shows three names (Branding, the Organization record, the product chrome) and two timezones (Edit Organization, Localization) — one place for each, or say which wins (hotcrm#2194 T2-U20, R2-T2-U10) #25347, in the queue) and open PR feat(plugin-auth): multi-org Create User joins an organization in the same step, defaulting to the admin's current one #25626, each on a different row. git merge-tree of this head with each of them, and with today's main, is clean. The merge queue rebuilds on whichever lands first and re-runs feature-gate-guard.test.ts.
  • Out of scope, disposition: none raised.
  • Mergeable: not governed. Clean onto main.
  • Landing: Tier S. The seat readies and arms the PR in this act, on the maintainer's standing word for this term (「按照 pm-dispatch 规则审核通过的issue,你可以自主落地」). Fixes #25337 closes the card completed on merge, as ruling 6370058183 says; the delete half needs no follow-up card.

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