Skip to content

feat(authz)!: group tenancy posture + org scope as a first-class dimension — ADR-0105 Phase 0/1#3559

Merged
os-zhuang merged 2 commits into
mainfrom
claude/adr-0105-group-tenancy-posture-5womi2
Jul 27, 2026
Merged

feat(authz)!: group tenancy posture + org scope as a first-class dimension — ADR-0105 Phase 0/1#3559
os-zhuang merged 2 commits into
mainfrom
claude/adr-0105-group-tenancy-posture-5womi2

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Implements ADR-0105 Phase 0 (correctness) and Phase 1 (group posture MVP).

Tracking: #3541 · Closes #3539 · Closes #3540 · Cloud counterpart: objectstack-ai/cloud#874


Kernel

D1 — tenancy is a spectrum: single | group | isolated. Selected by the new OS_TENANCY_POSTURE, resolved by the tenancy service. Unset derives from OS_MULTI_ORG_ENABLED (trueisolated, else single), so every existing deployment keeps its posture with no config change. An unrecognized value throws at boot rather than silently landing in a posture with no organization wall.

D2 — accessible_org_ids is a first-class context field. Every organization the caller holds a currently-valid membership in (ADR-0091 windows), resolved once by resolveAuthzContext and carried by every transport (REST, runtime, MCP, hono). The group Layer 0 wall is organization_id IN accessible_org_ids; empty or absent fails closed.

Only the predicate widens. Layer 0 is still computed independently of the RLS compiler, still AND-composed outermost, and still crossable only by a true PLATFORM_ADMIN on a posture-permitting object — so ADR-0095's W1/W2 hold in every posture. tenant-layer.test.ts asserts the exemption rules identically for group and isolated.

D5 — engine-owned stamping + write validation. Under any walled posture the engine stamps organization_id from the caller's active org on an insert that omits it, and validates every supplied value. Idempotent with the enterprise auto-stamp. This also closes a real hole: the pre-existing post-image check required a non-array payload, so a bulk insert could carry a forged organization_id per row — the #2937 defect one call site down. One forged row now denies the whole write. No tenant is ever invented: a caller with no active org leaves the value absent.

P0 correctness — the two behavior changes to review closely

D3 (#3539) — authored RLS policies are no longer silently dropped. collectRLSPolicies strips by provenance (identity against the shipped declaration), not by substring-matching current_user.organization_id.

⚠️ Upgrade impact: in a deployment with no organization wall, an app-authored policy referencing the active organization is now retained and fails closed (zero rows) with a one-time warning, where it previously vanished and the object read unscoped. getReadFilter shared the defect, so analytics/raw-SQL consumers were affected too.

D4 (#3540) — VAMA never crosses an organization boundary. Wall-less postures get a de-VAMA'd organization_admin_no_bypass, derived from organization_admin (never a second literal, so the two cannot drift), and the superseded variant is revoked whenever the posture changes.

⚠️ Upgrade impact: in single posture an org owner/admin keeps full CRUD but loses the blanket ownership/sharing/RLS bypass. Deliberate deployment-wide visibility stays available via admin_full_access or an explicit set — it just stops being a side effect of a better-auth membership role.

Model + product

  • D6sys_organization.parent_organization_id / sort_order as a reporting dimension only, with both red lines lint-enforced by the new validateOrgAxisRedLines (wired into os lint / os compile / os validate): no permission inheritance along the org axis, and no business-unit grants on platform-global objects.
  • D7 — extension fields on better-auth-managed objects ride the existing ADR-0092 whitelist (no new mechanism). A new guard derives better-auth's real field surface from getAuthTables() at the pinned version and fails the build on any name collision, so a library upgrade cannot silently take ownership of a column.

Enforce-or-remove (D11) — BREAKING

All three had zero runtime consumers, so nothing changes at runtime.

Surface Disposition Migration
PermissionSet.contextVariables removed a set → a registered membership resolver; a constant → a literal in the policy
Territory / TerritoryModel / TerritoryType removed multi-position × business-unit anchoring; dimension security gets its own ADR
ExecutionContext.rlsMembership productized register an IRlsMembershipResolver under rls-membership-resolver

The resolver seam is fail-closed by construction (an unresolved key makes its policies drop out) and kernel-owned keys (accessible_org_ids, org_user_ids, …) are reserved — an app cannot redefine the wall's own vocabulary.

⚠️ Founder decision embedded here (D12)

The group posture's enforcement ships open — the union wall, accessible_org_ids, D5 stamping/validation, the D3/D4 fixes and the D6 lints — because the correctness of a wall is never a paid feature (cloud ADR-0016 铁律「强制免费、治理收费」).

To keep that decision as small and separable as possible, isolated keeps its existing enterprise org-scoping probe, so the current commercial boundary for legal-entity isolation is unchanged by this PR. The consequence to sign off on: an open build set to OS_TENANCY_POSTURE=group gets a working, engine-enforced multi-org wall. That is the intended concession, but it re-draws the ADR-0081 D2 line and should be confirmed before merge.

Verification

  • pnpm build — 71/71 tasks
  • pnpm test — 131/131 tasks (plugin-security 580 tests, plugin-auth 480, spec 6815)
  • pnpm lint + all five repo grep guards (nul-bytes, doc-authoring, role-word, org-identifier, authz-resolver) — clean
  • Spec drift gates: check:api-surface, check:docs, check:spec-changes, check:upgrade-guide, check:liveness, check:skill-refs — all regenerated and in sync
  • 5 new rows in the dogfood authz conformance ledger

New/updated coverage: tenant-layer.test.ts (posture spectrum + W1/W2 across postures), platform-tenant-policies.test.ts (D3 provenance, both directions), rls-membership-resolver.test.ts (the seam has a producer; every failure mode narrows), managed-extension-fields.test.ts (collision guard vs. the live better-auth schema), validate-org-axis-red-lines.test.ts, plus posture cases in auto-org-admin-grant.test.ts and bulk-insert cases in security-plugin.test.ts.

Two full-suite runs each hit one different unrelated failure (cloud-connection marketplace seed, cli dist/utils/storage-driver.test.js); both pass in isolation and neither touches this diff — parallel-run flakiness, called out rather than papered over.

Not in this PR

Phase 2 (D8 scoped invitations, D9 cross-org approval targeting, D13 promotion tooling), D10 layered master data (blocked on its follow-up ADR), Phase 3 governance, and the console UI affordances the ADR wants alongside Phase 1 (org switcher as write context, "all my organizations" read views) — the backend now reports features.tenancyPosture so objectui can build them.

🤖 Generated with Claude Code

https://claude.ai/code/session_015FebXPaaGrLhGKw1LHPbpL


Generated by Claude Code

…nsion — ADR-0105 Phase 0/1 (#3541)

Implements ADR-0105 Phase 0 (correctness) and Phase 1 (group posture MVP).

Kernel:
- D1: tenancy becomes a spectrum — `single | group | isolated`, selected by
  `OS_TENANCY_POSTURE` and resolved by the `tenancy` service. Unset derives from
  `OS_MULTI_ORG_ENABLED`, so existing deployments are unchanged; an unrecognized
  value throws at boot rather than silently dropping the wall.
- D2: `ExecutionContext.accessible_org_ids` — the caller's org access set,
  resolved once by `resolveAuthzContext` under ADR-0091 validity windows and
  carried by every transport. The `group` Layer 0 wall is
  `organization_id IN accessible_org_ids` (MOAC union); empty/absent fails closed.
  Only the predicate widens — Layer 0 stays independent, AND-composed first, and
  crossable only by a true PLATFORM_ADMIN, so ADR-0095 W1/W2 hold in every posture.
- D5: the engine now stamps `organization_id` on insert under any walled posture
  and validates every supplied value — including BULK array payloads, which the
  pre-existing post-image check skipped entirely (#2937 one call site down).

P0 correctness (fixes #3539, #3540):
- D3: `collectRLSPolicies` strips by PROVENANCE, not by substring-matching
  `current_user.organization_id`. App-authored policies are retained, warned
  about once, and fail closed instead of vanishing (F1 / ADR-0049 class).
  `getReadFilter` shared the defect, so analytics reads were unscoped.
- D4: `viewAllRecords`/`modifyAllRecords` never cross an org boundary. Wall-less
  postures get a de-VAMA'd `organization_admin_no_bypass` variant — DERIVED from
  `organization_admin`, never a second literal — and the superseded variant is
  revoked whenever the posture changes (F2 / personal-org amplification).

Model + product:
- D6: `sys_organization.parent_organization_id` / `sort_order` as a reporting
  dimension, with both red lines lint-enforced (`validateOrgAxisRedLines`, wired
  into os lint/compile/validate): no permission inheritance along the org axis,
  and no business-unit grants on platform-global objects.
- D7: extension fields on better-auth-managed objects ride the ADR-0092
  whitelist; a new guard derives better-auth's real field surface from
  `getAuthTables()` at the pinned version and fails on any name collision.

Enforce-or-remove (BREAKING, no runtime behavior change — all three had zero
consumers):
- `PermissionSet.contextVariables` removed; `security/territory.zod.ts` removed.
- `ExecutionContext.rlsMembership` productized: register an
  `IRlsMembershipResolver` under `rls-membership-resolver`. Kernel-owned keys are
  reserved; an unresolved key makes its policies drop out (fail closed).

Edition boundary (D12): the `group` wall and its write-side twin ship OPEN — the
correctness of a wall is never paid (cloud ADR-0016). `isolated` keeps its
enterprise `org-scoping` probe, so the commercial boundary for legal-entity
isolation is unchanged by this PR.

Adds 5 ADR-0105 rows to the dogfood authz conformance ledger.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FebXPaaGrLhGKw1LHPbpL
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 27, 2026 5:05am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Jul 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 23 package(s): @objectstack/cli, @objectstack/core, @objectstack/lint, @objectstack/mcp, @objectstack/metadata, @objectstack/platform-objects, @objectstack/driver-memory, @objectstack/driver-sql, @objectstack/embedder-openai, @objectstack/knowledge-memory, @objectstack/knowledge-ragflow, @objectstack/plugin-approvals, @objectstack/plugin-audit, @objectstack/plugin-auth, @objectstack/plugin-dev, @objectstack/plugin-hono-server, @objectstack/plugin-security, packages/qa, @objectstack/rest, @objectstack/runtime, packages/services, @objectstack/spec, @objectstack/types.

135 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/actions-as-tools.mdx (via @objectstack/core, @objectstack/mcp)
  • content/docs/ai/agents.mdx (via @objectstack/mcp, @objectstack/spec)
  • content/docs/ai/index.mdx (via @objectstack/mcp)
  • content/docs/ai/knowledge-rag.mdx (via @objectstack/core, @objectstack/embedder-openai, @objectstack/knowledge-memory, @objectstack/knowledge-ragflow)
  • content/docs/ai/natural-language-queries.mdx (via @objectstack/core, @objectstack/mcp)
  • content/docs/ai/skills-reference.mdx (via packages/cli, @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli, @objectstack/rest, @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/mcp, @objectstack/rest, @objectstack/runtime, @objectstack/spec)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/approvals.mdx (via @objectstack/plugin-approvals, packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/cli, @objectstack/runtime, packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/core, packages/services, @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata, packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/core, packages/runtime, packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/driver-memory, @objectstack/driver-sql, @objectstack/runtime, @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli, @objectstack/plugin-audit, @objectstack/plugin-auth, @objectstack/plugin-security, @objectstack/spec)
  • content/docs/deployment/environment-variables.mdx (via @objectstack/mcp)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/core)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/plugin-audit, @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/driver-memory, @objectstack/runtime)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-memory, @objectstack/driver-sql)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli, @objectstack/driver-memory, @objectstack/plugin-hono-server, @objectstack/runtime, @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via packages/metadata, @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/audit-service.mdx (via packages/services)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/core)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli, packages/services, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/settings-service.mdx (via packages/services)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/core, @objectstack/driver-memory, @objectstack/plugin-auth, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/core)
  • content/docs/permissions/access-recipes.mdx (via packages/plugins/plugin-security)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli, @objectstack/core, @objectstack/driver-memory, @objectstack/plugin-auth, @objectstack/plugin-hono-server, @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/core, @objectstack/lint, @objectstack/mcp, @objectstack/plugin-security, packages/qa, packages/runtime, @objectstack/spec)
  • content/docs/permissions/delegated-administration.mdx (via packages/qa)
  • content/docs/permissions/explain.mdx (via @objectstack/plugin-security)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via packages/plugins/plugin-security, @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/permissions/sso.mdx (via @objectstack/plugin-auth)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/anatomy.mdx (via @objectstack/core, @objectstack/driver-sql)
  • content/docs/plugins/development.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/core, @objectstack/driver-memory, @objectstack/plugin-auth, @objectstack/plugin-hono-server, @objectstack/plugin-security, @objectstack/rest, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/cli, @objectstack/core, @objectstack/mcp, @objectstack/metadata, @objectstack/platform-objects, @objectstack/driver-memory, @objectstack/driver-sql, @objectstack/plugin-approvals, @objectstack/plugin-audit, @objectstack/plugin-auth, @objectstack/plugin-dev, @objectstack/plugin-hono-server, @objectstack/plugin-security, @objectstack/rest, @objectstack/runtime, packages/services, @objectstack/spec, @objectstack/types)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/rest, packages/services, @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/core, @objectstack/driver-sql, @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/core, @objectstack/driver-sql, @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/metadata-service.mdx (via @objectstack/metadata)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli, @objectstack/core, @objectstack/spec)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/mcp, @objectstack/knowledge-memory, @objectstack/knowledge-ragflow, @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/driver-memory, @objectstack/driver-sql, @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli, @objectstack/core, @objectstack/mcp, @objectstack/driver-memory, @objectstack/driver-sql, @objectstack/plugin-approvals, @objectstack/plugin-audit, @objectstack/plugin-auth, @objectstack/plugin-hono-server, @objectstack/plugin-security, @objectstack/rest, @objectstack/runtime, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/core, @objectstack/metadata, @objectstack/rest, @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v15.mdx (via @objectstack/core)
  • content/docs/releases/v16.mdx (via @objectstack/cli, @objectstack/plugin-hono-server, @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/plugin-approvals, @objectstack/plugin-auth, @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/audience-based-interfaces.mdx (via packages/plugins/plugin-security)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/platform-objects, @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

…major`

Every publishable package is in the Changesets `fixed` (lockstep) group, so a
single `major` promotes the ENTIRE monorepo to a new major version. The repo
ships breaking changes as `minor` during the launch window (enforced by
scripts/check-changeset-no-major.mjs); #3494's spec prune set the precedent.

The FROM → TO migration notes are unchanged — they are what reaches consumers in
CHANGELOG.md, and they carry the breaking-ness regardless of the bump level.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FebXPaaGrLhGKw1LHPbpL
@os-zhuang
os-zhuang marked this pull request as ready for review July 27, 2026 05:50
@os-zhuang
os-zhuang merged commit 879ea13 into main Jul 27, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/adr-0105-group-tenancy-posture-5womi2 branch July 27, 2026 05:50
os-zhuang added a commit that referenced this pull request Jul 27, 2026
… D12 correction (#3570)

Follow-up to #3559, which read ADR-0105 D12 as "the `group` wall ships open" and
made the posture self-activating — it never probed for @objectstack/organizations.
That turned `group` into a free multi-org path around the `isolated` gate
(ADR-0081 D2), leaving the WEAKER isolation as the free one, and it opened a
silent-degradation hole: `os serve` still gated the enterprise package load on
OS_MULTI_ORG_ENABLED, so OS_TENANCY_POSTURE=group skipped both the load and the
ADR-0093 D5 fail-fast.

The distinction missed: open code is not free activation. The wall's
implementation has always lived in open packages — equally true of `isolated`,
whose Layer 0 wall sits in plugin-security gated on a service the enterprise
package registers. Cloud ADR-0016's 铁律 guarantees a deployment RUNNING a
multi-org shape is safe; that is satisfied by REFUSING to run one unwalled, not
by giving the posture away.

- tenancy-service: `group` probes `org-scoping` exactly like `isolated`; without
  it the posture resolves to `single` + `degraded`.
- serve.ts: the ADR-0093 D5 boot guard keys off the resolved POSTURE, with
  posture-aware messaging. A `group` request without the runtime now refuses to
  boot unless OS_ALLOW_DEGRADED_TENANCY=1.
- New seam: `org-scoping` may declare `supportedPostures`
  (`OrgScopingEntitlement`); the open side honours it and fails closed on
  anything unlisted. Whether `group` and `isolated` are one commercial tier or
  two is packaging policy, which belongs to the commercial runtime rather than
  hard-coded in open core. Omitting it entitles every walled posture.
- `organization_id` STAMPING returns to the enterprise runtime. #3559 moved it
  into the open engine, removing the closed package's only load-bearing runtime
  duty — a five-line forged `org-scoping` registration would then have produced a
  fully working multi-org deployment. Back where it was, a forged registration
  yields NULL-org rows the wall hides. Write-side VALIDATION stays open and
  unchanged, bulk-insert coverage included: rejecting a forged organization_id is
  a security property, not a packaging one.
- Default-org bootstrap returns to `single`-only (ADR-0081 D1).
- Documents OS_TENANCY_POSTURE in the environment-variables reference.

Operator note: OS_TENANCY_POSTURE=group without the enterprise runtime now
refuses to boot rather than silently running single-org.
os-zhuang added a commit that referenced this pull request Jul 27, 2026
… ADR-0105 D1 (#3596)

Since #3559 a walled posture is an explicit operator request resolved from
env when AuthPlugin registers the `tenancy` service; mounting the enterprise
organizations plugin only ENTITLES it. bootStack's multiTenant opt-in predates
that split and only mounted the plugin, so multi-org fixtures silently booted
`single` — no Layer 0 wall, default-org write stamping — and every cross-tenant
proof asserted against the wrong posture. First surfaced by cloud's
security-enterprise multi-org integration test (the licensed path open-core CI
cannot run), which fails 3/4 against any framework checkout at or past #3559.

bootStack({multiTenant: true}) now sets OS_TENANCY_POSTURE=isolated for the
boot — before AuthPlugin snapshots the requested posture — unless the caller
already provided one; the request is restored on stop() and on a failed
multi-tenant boot, so later single-tenant boots in the same worker are
unaffected.

The verify package also gains a `test` script: `turbo run test` never ran its
suite before, so the new posture regression pin (and the existing derive tests)
now actually gate.

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jul 27, 2026
…led, not open (#3570) (#3603)

Founder ruling: OS_TENANCY_POSTURE=group is not an open-edition feature.
The code was already corrected in #3570 (group probes org-scoping exactly
like isolated; no runtime => single + degraded; an os serve boot configured
for group fail-fasts). This brings the ADR text in line:

- D12 rewritten to the code-vs-activation split: the wall's implementation
  ships open, posture activation is entitled for BOTH multi-org postures;
  iron-rule safety is satisfied by refusing to run an unwalled group boot,
  not by free activation. ADR-0081 D2's commercial line stands.
- Amendment block records the original text, why it was wrong (inverted
  ADR-0081 D2 + the silent-degradation hole), and the #3559 -> #3570 trail.
- Consequences clause updated to match; header Status moves to Accepted with
  the amendment note; Tracking points at #3541 / #3539 / #3540 / cloud #874.


Claude-Session: https://claude.ai/code/session_015FebXPaaGrLhGKw1LHPbpL

Co-authored-by: Claude <noreply@anthropic.com>
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/xl tests tooling

Projects

None yet

2 participants