docs(skills): state what single posture means for the organization count in objectstack-data - #17476
docs(skills): state what single posture means for the organization count in objectstack-data#17476os-litant wants to merge 1 commit into
single posture means for the organization count in objectstack-data#17476Conversation
…count in objectstack-data The published `objectstack-data` skill presented the object-level `tenancy` block as the multi-tenancy instruction and never said that the deployment posture decides how many organizations exist. An AI author reading it (plus the runtime's exemption comment) lands on "stay `single`, seed one `sys_organization` per tenant, hand-write the RLS wall" — the shape the deployment page forbids by name. - `rules/security.md` § Multi-tenancy: the posture ↔ organization-count contract (`single` = the bootstrapped Default Organization only, a second refused 403; tenants as organizations need a walled posture declared on the deployment, open core since ADR-0132), with ADR-0093 / ADR-0105 § "Today's two postures" / the tenancy-modes page as the pointer; `tenancy` re-presented as the row-level knob within a posture. - `SKILL.md` § Seed Data & Fixtures: `sys_organization` is platform-bootstrapped and never a seed target. - `references/data-hooks.md` § Two isolation axes: "multi-org needs cloud" and "Community edition never populates an org" rewritten to the ADR-0132 open-core truth and the `single` contract. The skills token ratchet is paid in the same edit: the sentences replaced are tightened, the database-per-tenant bullet in security.md is folded away (the fact stays in data-hooks.md § Two isolation axes), and no ceiling moves. Claude-Session: https://claude.ai/code/session_01YKEjmbYNvYWJvWGSWx26zK Co-authored-by: Claude <noreply@anthropic.com>
维护者速读改了什么 —— 发布技能 为什么改 —— 你在 #16934 问过「ats 为什么会搞错,是我们的 skills 或者文档没写清楚吗?」。答案是:是。契约写在部署文档第 48 行和 ADR-0105 / ADR-0131 里,而 AI 作者真正加载的技能只说 风险与代价(含回滚) —— 只改发布技能文本:不改 runtime、不改 席位意见 —— 通过。本席在契约复审档逐句对照来源: 你要做的 —— 一个动作:人工合并本 draft PR。 skills 席,session Generated by Claude Code |
Fixes #17019
Governed surface (
skills/**) — this PR stays DRAFT and the maintainer merges it (Prime Directive #14). Clause-②: no — published prose only; no schema accept set moves and no public surface widens (the sentences narrow what an author may believe).The contract, and where the skill lacked it
The deployment posture decides how many organizations exist:
tenancy-modes.mdx:48 「singleis not "multi-org with the walls left standing"」 and :138–140 (creating additional organizations is refused403undersingle); ADR-0105 :70 / :153 / :512 (one logical tenant; factories as business units in one tree); ADR-0093 (the posture ladder); ADR-0131 D9 (「undersinglethe Default Organization is the only one」). The publishedobjectstack-dataskill — the one text guaranteed to be in an AI author's context — carried none of it and pointed the other way in three places. Re-measured on this branch's base3644fadc(origin/main had moved on from the dispatch readingfd62a66b; all three premises held unchanged):3644fadc)f301e3ca)rules/security.md:155tenancyon the object schema for row-level tenant isolation」 — the object knob presented as the multi-tenancy instruction; no posture sentencesingle(default) = one logical tenant: the bootstrapped Default Organization only, a second refused (403); sub-units are business units. Tenants assys_organizationrows need a walled posture (group/isolated; open core, ADR-0132) — ⛔ neversingle+ your own RLS. See ADR-0093, ADR-0105 §"Today's two postures", https://objectstack.ai/docs/deployment/tenancy-modes.」 followed by 「Within a posture,tenancyon the object is the row-level knob (stamped on write, enforced on read) …」rules/security.md:180–181singledoes, never what it meanssinglethe wall is inert.」 — the meaning now lives one section upSKILL.md§ Seed Data & Fixturessys_organizationsys_organizationis platform-bootstrapped — never a seed target; the deployment posture decides how many exist (rules/security.md§ Multi-tenancy).」references/data-hooks.md:623@objectstack/organizations」OS_TENANCY_POSTURE=group|isolated— with@objectstack/organizationsdeclared by the app, open core since ADR-0132」references/data-hooks.md:629–630organizationIdisundefinedthere」singlethe bootstrapped Default Organization is the only one, so there is nothing to scope by (rules/security.md§ Multi-tenancy).」Reader of every added line: an AI author choosing between
tenancyon an object schema and a posture declared on the deployment.Re-check greps. Base:
git grep -ciE 'one logical tenant|Default Organization' -- skills/= 0 (control overcontent/docs/deployment/:environment-variables.mdx:1,tenancy-modes.mdx:5). Onf301e3ca: 「one logical tenant」 underskills/→rules/security.md:156(was 0); the card's four-term alternation (exactly one organization|one logical tenant|Default Organization|sys_organization) →security.md:2,SKILL.md:1,data-hooks.md:1(was 0); 「For SaaS, set .tenancy」 insecurity.md→ 0 (was 1); 「needs cloud」 indata-hooks.md→ 0 (was 1).Zone 2 — mechanism assumptions, measured
3644fadcbefore writing (quoted in the table).c677cda8; RESTpulls/16215→merged: true); test(organizations): prove an open-only install RAISES the wall — the matrix against a resolved posture, no stub (#16137) #16719 merged 2026-09-08T02:13Z (5e53d73d;merged: true);packages/plugins/organizationsis onorigin/mainand its acceptance test pinssupportedPostures=['group', 'isolated'](src/open-only-wall-acceptance.test.ts:592). The shallow clone (108 commits) cannot see either landing, so the REST state is the reading. The clause is written as 「open core, ADR-0132」 / 「open core since ADR-0132」.@objectstack/organizationsenterprise/closed-source in 65 places — includingtenancy-service.ts, the file that decides the posture #16718 is an issue (closed); its fix PR docs(organizations): stop calling the open multi-org runtime enterprise/closed-source (#16718) #17371 merged 2026-09-10T10:50Z and touched no file underskills/(git show --stat 50bc9c73 -- skills/is empty), so :623 was still stale on base and is rewritten here rather than left. Against ADR-0132 D1/D3/D4 (the runtime ships from this repo, Apache-2.0; an app declares@objectstack/organizationsfrom npm; the open package entitles both walled postures) the sentence is rewritten, not deleted — the two-axes distinction it carries is still correct, only its edition claim was stale.git diff --name-only origin/main...HEAD= exactly the three skill files.origin/mainmoved3644fadc→ab489388during the run and touched nothing underskills/(git diff --name-only 3644fadc origin/main -- skills/is empty), so no rebase was needed.Judgement calls, on the four axes
Frame (SKILL.md :734–:752, axes verbatim): 实际业务需求 · 项目长远合理性 · 防 AI 写代码犯错,尤其是防 AI 写元数据 app 犯错 · 创业阶段不扩散需求.
1. Paying the token ratchet.⚠️ blockquote at :197–:209 (nobody's claim, but a declared-surface breach); C report
rules/security.mdsat at headroom 0 (2480/2480) andSKILL.mdat headroom 10 tokens. The sentence being replaced (180 bytes) cannot pay for a contract paragraph (~480 bytes), so the Zone-1 line "tighten the sentences you replace" was arithmetically insufficient on its own — a first draft that only tightened those sentences landed +444 bytes over the ceiling. Options: A pay by deleting lower-value content inside the declared region; B expand the region into theblocked. Chosen A. 实际业务需求: the reader needs the contract more than a restated third axis. 项目长远合理性: no ceiling moves and no region breach. 防 AI 犯错: the deleted bullet 「Database-per-tenant isolation is not object metadata」 survives verbatim in the same skill'sdata-hooks.md§ Two isolation axes, so no fact leaves the bundle; the code comment 「// enable row-level tenant isolation」 restated the prose above it. 创业阶段不扩散: the addition is exactly the four readers' lines andsecurity.mdends 5 bytes smaller. Deleted (all inside :153–:181): that bullet, that code comment, and 「(not partitioned per org)」 / 「These hit a non-obvious interaction:」 / 「common for adapter-written rows that never get the tenant stamp」 respelled shorter with the same content.2. Where the pointer lives (Zone 3 step 3 — deviation). The route said "extend the Sources line at :210" (「Posture model: ADR-0066; tenant wall: ADR-0095 D1」). That line documents the visibility posture of ADR-0066 — a different sense of "posture" — and appending the tenancy-posture sources there would conflate the two for the very reader this card is about. The pointer (ADR-0093 / ADR-0105 § "Today's two postures" / the tenancy-modes page) sits in the Multi-tenancy paragraph next to the sentence it substantiates; :210 is untouched. 防 AI 犯错 decides it; the other three axes are neutral (same token cost either way).
3. The docs pointer is the published URL,
https://objectstack.ai/docs/deployment/tenancy-modes, notcontent/docs/deployment/tenancy-modes.mdx: the skill is installed into customer projects (npx skills add …) where the repo path does not exist;SKILL.md:612 already cites docs this way. The route is listed incontent/docs/deployment/meta.json:7. 实际业务需求 and 防 AI 犯错 both point the same way; the URL costs 5 more bytes, paid in the same edit.4.
data-hooks.md:629–:630 — a two-line increment to the declared surface, declared here. The claim named the :622–:623 sentence. The closing sentence of the same blockquote asserts the same stale edition split (ADR-0132 makes the edition irrelevant to the posture) and is wrong on its own terms: undersinglea session carries the Default Organization (packages/plugins/plugin-auth/src/ensure-default-organization.ts:6–:7, 「so their sessions can carry anactiveOrganizationId」). Leaving it would have put 「open core since ADR-0132」 and 「Community edition never populates an org」 in one paragraph. The in-place-fix exemption's four conditions hold: same defect class (a stale posture/edition claim in this skill); mechanical fix with the shape pinned by ADR-0132 D1/D3 and ADR-0131 D9; no other claim holds this file (#17359 is onsecurity.md:33 and blocked; #17371 is merged and leftskills/untouched); same gate family, no new verification surface. The identical sentence inpackages/*/CHANGELOG.mdandcontent/docs/releases/v16.mdxis RELEASE-OWNED and untouched.Verification
Tree
objectstack-ai/objectstackatf301e3ca(merge-base3644fadc); every exit code captured before any pipe.node scripts/pm/check-governed-merges.mjs --teston the three files → exit 3, 「⛔ GOVERNED — a human merge is the review record for this PR (governance: human merge IS the review record for governed surfaces — retire the per-PR ADR approval check, add a report-only post-merge audit #9495 regime)」,skills/** ×3.node scripts/pm/dispatch-gates.mjs --tieron the three files → 「Model tier — MANDATORY: claude-fable-5-1 (derived from the file surface, not recalled)」.node scripts/pm/dispatch-gates.mjs --commands(no paths; the changeset from the merge-base, three-dot) → 25 commands;--ran→ 「✓ dispatch-gates --ran: 25 derived famil(ies) accounted for — 25 run, 0 NOT-MEASURED」.node scripts/check-skills-token-ratchet.mjs(onf301e3ca)security.md2479/2480 (headroom 1),SKILL.md10006/10009 (headroom 3),data-hooks.md9781/12611node scripts/check-skills-token-ratchet.mjs --self-testpnpm --filter @objectstack/spec run check:skill-docspnpm --filter @objectstack/spec run check:skill-refspnpm check:skill-frame-syncnode scripts/check-doc-route-spelling.mjs --advisory/--self-testpnpm check:nul-bytespnpm check:skill-identifier-livenesspnpm check:skill-compatibilitypnpm check:doc-authoringpnpm check:corpus-claim-driftpnpm --filter @objectstack/lint run check:doc-formula-expressionsturbo run build --filter=@objectstack/formula --filter=@objectstack/lintunder the verify lock (「VERDICT command-exit 0 · held the lock 184s」); the first attempt returned 「PREREQUISITE NOT MET」 exit 3, which is NOT MEASURED, not a findingnode scripts/check-ci-filter-parity.mjs·check-closing-keyword-parity.mjs(+--self-test) ·check-comment-mask-corpus.mjs·report-test-timings.mjs --self-test·pnpm check:agent-test-spelling·check:cross-package-test-inputs·check:driver-memory-census·check:pm-governed-merges·check:refd-timer-probe·check:role-word·check:watch-hint-literalChangeset: none — no package under
packages/**changes, and no released package'sfiles[]shipsskills/(create-objectstackfiles[]=dist,README.md,CHANGELOG.md; the catalog reaches customers bynpx skills addfrom the git tree) ⇒skip-changeset. Control-character self-scan over the three files: 0 hits. Package build/test (local scope ①/②): no package touched ⇒ none owed; theformula/lintbuild above was a gate prerequisite only. Not run locally, CI's: the repo-widepnpm lintand the artifact-roster families.Skills-bundle readings (lines; tokens = ceil(utf8 bytes / 4), the ratchet's own convention)
skills/objectstack-data/rules/security.mdskills/objectstack-data/SKILL.mdskills/objectstack-data/references/data-hooks.mdskills/*/SKILL.md(whole published catalog)Net +6 lines across three files; no ceiling raised.
Acceptance notes
main(by its own text the merge is the acceptance act); a one-line status update is a doc nit. 承接者:无.content/docs/deployment/tenancy-modes.mdx:35 saysorganization_idis not filled in on write undersingle, while ADR-0131 D9 (Accepted 2026-09-04) says a missing stamp is derived to the Default Organization undersingle. The skill text here relies only on the organization count, on which both agree; whether the table row is stale is a docs question outside this card's surface. 承接者:无.rules/indexing.md:77 「You never write the posture」 (named by the card) is outside the declared surface and untouched; with the Multi-tenancy section now stating what the posture means, it reads as intended ("an index declaration is posture-independent").isDefault→everyonebinding rule (after #17189) #17359 (blocked on An app-declaredcapabilitywithscope: 'org'counts as a high-privilege bit, so a set carrying only app capability tokens cannot bind to theeveryoneanchor #17189) ownsrules/security.md:33; this diff touches :153–:181 only — region-disjoint, as the claim declared. [finding] Asingle-posture deployment holding more than onesys_organizationrow boots silently — ADR-0131 §1.2(3) calls that precondition 「a refused boot」 and it is not; the harm surfaces five cards away (platform admin reads 0 rows on/data, system writes refused by #8844) #17010 (the boot-time organization count) remains open and is the runtime half.维护者速读(草稿)
objectstack-dataskill 里补上「部署 posture 决定组织数量」这条契约:single= 只有平台自举的 Default Organization,再建即403;把租户建成sys_organization行必须在部署上声明 walled posture(group/isolated,ADR-0132 之后是开源能力),⛔ 不是single+ 自写 RLS。种子章节加一行 ⛔sys_organization不是 seed 目标;hooks 参考里两句过时的「多组织需要 cloud / 社区版不填组织」改成现状。singleposture, which tenant wall is the platform's? driver-sql's posture-independenttenantIdauto-scope answers a platform admin 0/0/0/0 on/datawhile the engine path, analytics and the memory driver answer 12/30/40/14 #16934 里您问「ats 为什么会搞错,是我们的 skills 或者文档没写清楚吗?」——答案是 skill 没写。AI 作者读 skill 时只看到对象级tenancy开关和「single 下墙惰性」,于是得出 ats 那套「留在 single、每租户一个组织、自己立墙」的设计;禁止这种形态的句子只在tenancy-modes.mdx和 ADR-0105 里,skill 没有指过去。data-hooks.md重复的 bullet 和精简措辞付账。回滚 = revert 本 PR 的一个 commit,无数据、无运行时影响。skills/**:AI 席位不合并、不排队、不翻 ready)。Generated by Claude Code