Skip to content

docs(skills): state what single posture means for the organization count in objectstack-data - #17476

Draft
os-litant wants to merge 1 commit into
mainfrom
claude/issue-17019-data-skill-single-posture
Draft

docs(skills): state what single posture means for the organization count in objectstack-data#17476
os-litant wants to merge 1 commit into
mainfrom
claude/issue-17019-data-skill-single-posture

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

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 「single is not "multi-org with the walls left standing"」 and :138–140 (creating additional organizations is refused 403 under single); ADR-0105 :70 / :153 / :512 (one logical tenant; factories as business units in one tree); ADR-0093 (the posture ladder); ADR-0131 D9 (「under single the Default Organization is the only one」). The published objectstack-data skill — 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 base 3644fadc (origin/main had moved on from the dispatch reading fd62a66b; all three premises held unchanged):

site before (base 3644fadc) after (f301e3ca)
rules/security.md :155 「For SaaS, set tenancy on the object schema for row-level tenant isolation」 — the object knob presented as the multi-tenancy instruction; no posture sentence 「Organization count is a deployment posture fact, never object metadata. single (default) = one logical tenant: the bootstrapped Default Organization only, a second refused (403); sub-units are business units. Tenants as sys_organization rows need a walled posture (group/isolated; open core, ADR-0132) — ⛔ never single + your own RLS. See ADR-0093, ADR-0105 §"Today's two postures", https://objectstack.ai/docs/deployment/tenancy-modes.」 followed by 「Within a posture, tenancy on the object is the row-level knob (stamped on write, enforced on read) …」
rules/security.md :180–181 「Single-tenant deployments never hit this; the wall is inert there」 — what single does, never what it means 「Under single the wall is inert.」 — the meaning now lives one section up
SKILL.md § Seed Data & Fixtures nothing about sys_organization 「⛔ sys_organization is platform-bootstrapped — never a seed target; the deployment posture decides how many exist (rules/security.md § Multi-tenancy).」
references/data-hooks.md :623 「multi-org needs cloud + @objectstack/organizations 「it needs a walled posture — OS_TENANCY_POSTURE=group|isolated — with @objectstack/organizations declared by the app, open core since ADR-0132」
references/data-hooks.md :629–630 「Community edition never populates an org, so organizationId is undefined there」 「Under single the 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 tenancy on 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 over content/docs/deployment/: environment-variables.mdx:1, tenancy-modes.mdx:5). On f301e3ca: 「one logical tenant」 under skills/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」 in security.md → 0 (was 1); 「needs cloud」 in data-hooks.md → 0 (was 1).

Zone 2 — mechanism assumptions, measured

Judgement calls, on the four axes

Frame (SKILL.md :734–:752, axes verbatim): 实际业务需求 · 项目长远合理性 · 防 AI 写代码犯错,尤其是防 AI 写元数据 app 犯错 · 创业阶段不扩散需求.

1. Paying the token ratchet. rules/security.md sat at headroom 0 (2480/2480) and SKILL.md at 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 the ⚠️ blockquote at :197–:209 (nobody's claim, but a declared-surface breach); C report blocked. 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's data-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 and security.md ends 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, not content/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 in content/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: under single a session carries the Default Organization (packages/plugins/plugin-auth/src/ensure-default-organization.ts :6–:7, 「so their sessions can carry an activeOrganizationId」). 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 on security.md :33 and blocked; #17371 is merged and left skills/ untouched); same gate family, no new verification surface. The identical sentence in packages/*/CHANGELOG.md and content/docs/releases/v16.mdx is RELEASE-OWNED and untouched.

Verification

Tree objectstack-ai/objectstack at f301e3ca (merge-base 3644fadc); every exit code captured before any pipe.

family exit verdict line (the gate's own)
node scripts/check-skills-token-ratchet.mjs (on f301e3ca) 0 「✓ check-skills-token-ratchet: 34 authored bundle file(s) within their ceilings; 10 generator-owned file(s) measured, not ratcheted.」 — security.md 2479/2480 (headroom 1), SKILL.md 10006/10009 (headroom 3), data-hooks.md 9781/12611
node scripts/check-skills-token-ratchet.mjs --self-test 0 「✓ check-skills-token-ratchet self-test: 65 cases pass.」
pnpm --filter @objectstack/spec run check:skill-docs 0 「✅ Skill docs in sync」
pnpm --filter @objectstack/spec run check:skill-refs 0 「✅ 9 generated files in sync with packages/spec」
pnpm check:skill-frame-sync 0 「✓ check-skill-frame-sync: the one declared copy of the decision frame is internally coherent … 74 markdown files scanned for undeclared copies.」
node scripts/check-doc-route-spelling.mjs --advisory / --self-test 0 / 0 「✓ route-spelling guard (advisory): population clean — every shape-matched literal spells its ledger row.」
pnpm check:nul-bytes 0 「check-nul-bytes: OK (scanned 8278 text file(s) … no raw ASCII control bytes).」
pnpm check:skill-identifier-liveness 0 「check-skill-identifier-liveness OK — Leg 1: 457 citation(s) over 44 published file(s) checked against 101257 implementation word tokens」
pnpm check:skill-compatibility 0 「✓ check-skill-compatibility-version: 10 SKILL.md file(s) reconciled against 80 workspace packages」
pnpm check:doc-authoring 0 「✓ doc authoring guard: sibling-package prose ids hold the baseline — 821 pinned site(s) across 231 file(s) … no growth」
pnpm check:corpus-claim-drift 0 「check-corpus-claim-drift: OK, no new claim sites beside a pinned spelling.」
pnpm --filter @objectstack/lint run check:doc-formula-expressions 0 「✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 438 files / 1375 TS blocks judged clean」 — after turbo run build --filter=@objectstack/formula --filter=@objectstack/lint under 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 finding
node 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-literal 0 each whole-tree families placed by the derivation; their verdict lines are in the report comment on #17019

Changeset: none — no package under packages/** changes, and no released package's files[] ships skills/ (create-objectstack files[] = dist, README.md, CHANGELOG.md; the catalog reaches customers by npx skills add from 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; the formula/lint build above was a gate prerequisite only. Not run locally, CI's: the repo-wide pnpm lint and the artifact-roster families.

Skills-bundle readings (lines; tokens = ceil(utf8 bytes / 4), the ratchet's own convention)

file lines before → after tokens before → after (ceiling)
skills/objectstack-data/rules/security.md 210 → 211 2480 → 2479 (2480)
skills/objectstack-data/SKILL.md 851 → 854 9999 → 10006 (10009)
skills/objectstack-data/references/data-hooks.md 983 → 985 9741 → 9781 (12611)
all skills/*/SKILL.md (whole published catalog) 6127 → 6130

Net +6 lines across three files; no ceiling raised.

Acceptance notes

维护者速读(草稿)

  • 改了什么:在对外发布的 objectstack-data skill 里补上「部署 posture 决定组织数量」这条契约:single = 只有平台自举的 Default Organization,再建即 403;把租户建成 sys_organization 行必须在部署上声明 walled posture(group/isolated,ADR-0132 之后是开源能力),⛔ 不是 single + 自写 RLS。种子章节加一行 ⛔ sys_organization 不是 seed 目标;hooks 参考里两句过时的「多组织需要 cloud / 社区版不填组织」改成现状。
  • 为什么改:[Decision] Under single posture, which tenant wall is the platform's? driver-sql's posture-independent tenantId auto-scope answers a platform admin 0/0/0/0 on /data while 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 没有指过去。
  • 风险与代价(含回滚):纯文案,三个文件净增 6 行;token 棘轮不抬上限,靠删掉一条与 data-hooks.md 重复的 bullet 和精简措辞付账。回滚 = revert 本 PR 的一个 commit,无数据、无运行时影响。
  • 席位意见:(留空,席位定稿)
  • 你要做的:确认契约表述无误后人工合并(受管面 skills/**:AI 席位不合并、不排队、不翻 ready)。

Generated by Claude Code

…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>

Copy link
Copy Markdown
Collaborator Author

维护者速读

改了什么 —— 发布技能 skills/objectstack-data 三处。① rules/security.md 「## Multi-tenancy」 开头新增契约段:组织数由部署姿态决定、不是对象元数据;single(默认)= 一个逻辑租户 = 只有引导出来的 Default Organization,再建一个被拒(403),子单位用 business unit;要把租户建成 sys_organization 行必须走带墙姿态 group/isolated(开源核心,ADR-0132)—— ⛔ 永不 single + 自己写 RLS;指向 ADR-0093、ADR-0105 §"Today's two postures" 和 tenancy-modes 文档页。原来的「For SaaS, set tenancy…」改写成「在姿态之内,tenancy 是对象级行隔离旋钮」。② SKILL.md §Seed Data & Fixtures 加一条 ⛔:sys_organization 由平台引导、永不作 seed 目标。③ references/data-hooks.md 把过时的「multi-org needs cloud」和「Community edition never populates an org」换成 ADR-0132 之后的事实。token 棘轮在段内付清(security.md 2479/2480、SKILL.md 10006/10009),上限一个没动;为付预算删掉的「Database-per-tenant 不是对象元数据」那条,在 data-hooks 同一段里原样活着。

为什么改 —— 你在 #16934 问过「ats 为什么会搞错,是我们的 skills 或者文档没写清楚吗?」。答案是:是。契约写在部署文档第 48 行和 ADR-0105 / ADR-0131 里,而 AI 作者真正加载的技能只说 single 下「墙是惰性的」,从没说它「意味着只有一个组织」;读技能加代码里的豁免注释,自然落到 ats 那个形状(single 下按雇主建组织行、自己写 RLS、以后再立墙)—— 正是文档点名禁止的形状。现在契约放在作者会读的那一段里。

风险与代价(含回滚) —— 只改发布技能文本:不改 runtime、不改 tenancy-modes.mdx、不改任何 schema;--pair 无扩面信号。代价:技能 token 预算用到边(security.md 只剩 1 token 余量),下次往这个文件加字要先减字。CI 在 head f301e3ca 上收敛中(6 项在跑)。回滚 = revert 本 PR 的三个文件。两条记下没立卡:ADR-0132 状态行还写着 Proposed(文件已在 main);tenancy-modes.mdx:35 与 ADR-0131 D9 对 singleorganization_id 是落库还是推导说法不一 —— 技能只依赖组织数,这点两边一致。

席位意见 —— 通过。本席在契约复审档逐句对照来源:single 默认(mdx:30)、403(mdx:138)、business unit(mdx:34)、group/isolated 开源(ADR-0132 D1/D4,测试钉死 supportedPostures = ['group','isolated'])、OS_TENANCY_POSTURE(mdx:72)、「不是留着墙的多组织」(mdx:48)—— 每句有出处、每句有具名读者;为换预算砍掉的内容逐条核过都有家。dev 有一处偏离建议路线:指针没接在 :210 那行(那行是 ADR-0066 的 visibility posture,另一种「posture」,接上去会把两个概念混给同一个读者),放进了契约段 —— 对。受管面(skills/**):本席不翻 ready、不入队、不批准。

你要做的 —— 一个动作:人工合并本 draft PR。

skills 席,session session_01YKEjmbYNvYWJvWGSWx26zK,2026-09-10T16:28Z。


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 needs-user-decision size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

1 participant