Skip to content

docs(organizations): stop calling the open multi-org runtime enterprise/closed-source (#16718) - #17371

Merged
hotlong merged 3 commits into
mainfrom
claude/organizations-open-core-prose-16718c
Sep 10, 2026
Merged

docs(organizations): stop calling the open multi-org runtime enterprise/closed-source (#16718)#17371
hotlong merged 3 commits into
mainfrom
claude/organizations-open-core-prose-16718c

Conversation

@hotlong

@hotlong hotlong commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Clause-②: no — this PR puts no new key on any published payload. Declared by the PM seat rather than the author, and measured rather than assumed: every changed line under packages/**/src/** is a comment or contains a string literal — filtering the diff for lines that are neither returns nothing, so no control flow moved and no identifier changed. The changed strings are operator-facing message text, not payload keys.

Fixes #16718

ADR-0132 moved the org-scoping registrar into open core. The prose did not follow: the open tree still called @objectstack/organizations enterprise / closed-source / cloud-private in the file that decides the tenancy posture, in the CLI that mounts it, and in the operator message that tells you how to install it.

Text only. Every hunk is a comment or a string literal — no control flow, no identifier, no behaviour change anywhere.

The two sites the card named

packages/plugins/plugin-auth/src/tenancy-service.ts — open core's own authority for resolving the posture. Its header said both walled postures require the enterprise package and that multi-organization operation is a commercial capability (ADR-0105 D12); isolationActive argued "Open code, entitled activation ... ENABLING a multi-organization posture is an entitlement". Rewritten around ADR-0132 D2/D3/D4. The mechanism is unchanged and says so — both walled postures still probe the same registrar, and both still degrade without it. What changed is the answer to "can an open install have it?", which is the question a reader opens this file to ask.

packages/cli/src/commands/serve.ts — the mount block no longer says the runtime "lives in the closed-source @objectstack/organizations package", and the operator-facing install remedy no longer ends:

NOTE: this runtime is closed-source and is NOT on the public npm registry — it is distributed with an enterprise / cloud subscription. Without one this bullet is not followable, and one of the two below is your path

That sentence told an open-source operator the remedy they had just been given was unusable. Verified before rewriting it rather than assumed: npm view @objectstack/organizations version license17.4.0, Apache-2.0.

Every rewritten site says WHICH package it means

ADR-0132 D3 makes one name denote two packages, so a sentence ambiguous between them is not corrected, it is differently wrong. Each rewritten site names the open Apache-2.0 build, or the commercial private subclass, or explicitly both. Where a site was load-bearing on ADR-0105 D12, the citation is repointed at D12 as amended by ADR-0132 rather than deleted — the amendment is on ADR-0105's own status line, dated 2026-09-06.

The accounting — what was left alone, and why

This is the number the card asked for, because "I edited fewer files than the count" is otherwise indistinguishable from missing some.

The card's own pattern, re-run at the merge base e669e5eee (the card measured 65 at 2e6a2ea4c; main has drifted +1 since):

files hits
merge base e669e5eee 45 66
this branch 076f1d54e 10 18
corrected 35 48

Plus 9 statements the card's pattern cannot see, all in files the sweep was already editing — five neighbouring cloud-private / enterprise package spellings in serve.ts, one ENTERPRISE in capitals in dev-plugin.ts (the pattern is case-sensitive, and this hit sat two lines above one that was counted), one in auth-plugin.ts, and two in the dogfood probe. 57 statements corrected in total.

The 18 that remain — deliberately

Population 2 — still true, about the OTHER package (2 hits, 2 files). Both describe cloud's genuinely private same-named subclass, mounted in cloud's own apps/objectos-ee, in no-framework-dependents.pin.test.ts's terms:

  • packages/rest/src/single-kernel-isolated-api-key-matrix.test.ts:21 — "cloud#1982 reproduced it on apps/objectos-ee with the REAL cloud-private @objectstack/organizations mounted"
  • packages/rest/src/single-kernel-isolated-session-org-claim-matrix.test.ts:11 — same shape

Population 3 — the "not installable in this workspace" cluster (15 hits, 6 files). packages/verify/src/harness.ts (6), harness.posture.test.ts (2), harness.posture-only.test.ts (1), harness.host-resolution.test.ts (1), packages/cli/test/serve-organizations-host-resolution.e2e.test.ts (3), and two dogfood files (2). Reported, not changed — and the reporting turned up something the card could not have known:

⚠️ The card says "It is now installable". Measured, that is only half true, and rewriting these sites on it would have made them newly false. packages/plugins/organizations/src/no-framework-dependents.pin.test.ts — "the mechanical half of ADR-0132's entitlement boundary" — asserts that no workspace package under packages/** may declare @objectstack/organizations in any of the four dependency fields, because the commercial repo consumes the framework by link: and such a dependency would put the ungated class inside the framework tree a commercial app links against. Its own text: "Adding such a dependency is a decision about the commercial boundary and it has to be argued on an ADR, not merged as a manifest line." Verified at 076f1d54e: git grep -l '"@objectstack/organizations"' -- '**/package.json' returns exactly one file, the package's own manifest.

So those six sites are stale in their reason but still correct in their effect. Filed as #17369 with the three routes and the pin's own exclusion of apps/* / examples/* (a host app declaring the runtime is the supported wiring) as the way through.

One more (1 hit, 1 file). serve-organizations-message-spelling.test.ts:72 is a hit on this PR's own rationale block, which quotes the old sentence it replaced. Correct as a historical quotation.

packages/plugins/organizations/** is untouched — its prose is already right.

The pin tests were updated deliberately, and are not relaxed

serve-organizations-message-spelling.test.ts (5 hits) and doctor-organizations-message-spelling.test.ts exist to pin operator wording, so they are part of the change; each carries a dated in-file block giving the reason. They still compare whole rendered lines built from Serve.ORGANIZATIONS_RUNTIME_PKG, and both CONTROL blocks still differ from their expectation only in spacing.

Proven by ablation rather than asserted, on the committed tree, with the mutation confirmed on disk by occurrence count both ways and the restore proved by git diff HEAD and blob-hash equality with HEAD (both files, all four checkpoints). Both pin tests resolve their subject through relative source imports inside packages/cli (./serve.js, ../utils/tenancy-posture-hints.js), so no dist/ is in the path and no rebuild leg applies.

leg mutation exit what fired
A re-insert enterprise into the isolated posture hint 1 3 failures across both pin files, including doctor's CONTROL — these pins can say no
B re-insert enterprise into serve's install-remedy bullet 1 2 failures, including serve's #12151 CONTROL — these pins can say no
control restored tree 0 19 passed (19)

Verification

All run on this branch at 076f1d54e, real output watched.

  • pnpm build --concurrency=2Tasks: 73 successful, 73 total
  • Tests, 14 affected packages@objectstack/core 1268 · @objectstack/plugin-auth 2245 · @objectstack/plugin-security 2103 · @objectstack/runtime 3532 · @objectstack/plugin-sharing 910 · @objectstack/dogfood 1064 (+3 skipped) · @objectstack/types 597 · @objectstack/service-storage 593 · @objectstack/service-settings 578 · @objectstack/service-datasource 676 · @objectstack/platform-objects 561 · @objectstack/mcp 292 · @objectstack/plugin-dev 72 · @objectstack/cli 3015 — all green except the 10 @objectstack/cli failures below.
  • pnpm turbo run typecheck over the same 14 packagesTasks: 78 successful, 78 total
  • pnpm lint (the repo's only style authority, eslint . --no-inline-config, whole repo — not narrowed) → exit 0, no output
  • Gates — all exit 0, quoting each one's own verdict line: check:nul-bytes ("scanned 8173 text file(s) ... no raw ASCII control bytes"), check:auth-mount-ledger ("19 ObjectStack auth mount(s), all accounted for"), check:verify-stand-in ("2 stand-in check(s) guarded ... 10 call site(s) reached"), check:cross-package-test-inputs ("28 package(s) read outside themselves, all declared"), check:test-source-alias ("73 packages with tests scanned"), check:org-identifier ("2653 author-facing source file(s)"), plus check:tenant-chokepoint, check:single-claim-paths, check:watch-hint-literal, check:error-code-casing. The gate family was derived with node scripts/pm/dispatch-gates.mjs (which reported 60+ families for this path set); this is a declared narrowing to the ones this diff can plausibly move — CI runs the farm regardless.

The 10 @objectstack/cli failures are a macOS artifact, proven, not waved away

3 files, 10 tests: test/published-subpath-console.pin.test.ts, test/published-subpath-hook-body.pin.test.ts, test/serve-runtime-state-project-key.test.ts. Every assertion is a path comparison of the same shape:

Expected: "/tmp/os-cli-subpath-MGca4r/consumer/node_modules/@objectstack/cli/dist/hook-body.js"
Received: "/private/tmp/os-cli-subpath-MGca4r/consumer/node_modules/@objectstack/cli/dist/hook-body.js"

/tmp is a symlink to private/tmp on macOS; the tests build scratch dirs from mkdtempSync(join(tmpdir(), …)) and compare the unresolved path against a child process's realpath-resolved one. Controlled experiment rather than an assertion: re-running exactly those three files with TMPDIR=/private/tmp and nothing else changed → Test Files 3 passed (3) · Tests 41 passed (41). No source change can put /private on one side of that comparison and not the other. CI runs on Linux, where /tmp is not a symlink.

Provenance — this branch salvages an earlier attempt

Two dispatched devs died on this card before pushing anything, and the branch they left on the remote (claude/organizations-is-open-core-prose-16718, tip 05374bee6) really was empty. But the local worktree of attempt 2 held an unpushed commit, 78cc23f8d, carrying a complete 39-file sweep. It is the first commit here, cherry-picked onto current origin/main; three conflicts (sharing-plugin.ts, admin-routes.ts, storage-service-plugin.ts) were main's #16013 comment refactor, resolved to main's text with the prose fix reapplied — those three now name the package instead of calling it "the enterprise organizations runtime". Every factual claim in the salvaged commit was re-verified against main before it was kept: ADR-0132 D2/D3/D4 exist and say what it cites, ADR-0105's status line carries the 2026-09-06 amendment, organizations-plugin.ts:194 declares ['group', 'isolated'], index.ts:31 states the no-licence-check boundary, and the npm publication is real.

Changeset

.changeset/organizations-open-core-prose.md, patch for @objectstack/cli and @objectstack/plugin-dev — the only two packages where a published, user-visible string moved (serve's install remedy, the isolated posture hint rendered by serve and doctor, os verify's --org-scoped flag description, and the dev stack's degraded-tenancy warning + stage-2 mount refusal). Every other edited package changed comments or test expectations only.

🤖 Generated with Claude Code

hotlong and others added 3 commits September 10, 2026 17:09
…se/closed-source (#16718)

ADR-0132 moved the `org-scoping` registrar into open core:
`packages/plugins/organizations` is Apache-2.0, carries no licence check of
any kind, and declares `['group','isolated']` as its own constant. #16137's
acceptance proved an open-only composition raises the `isolated` wall with no
licence check reachable. The prose did not follow — 65 occurrences across 45
files still described that package as enterprise / closed-source /
cloud-private.

Prose only. Every non-comment hunk is a string literal (an operator message, a
flag description, a conformance-matrix note, a test expectation); no control
flow, no identifier and no behaviour changes anywhere.

Corrected (population 1), led by the two sites the card names:

- `plugin-auth/src/tenancy-service.ts` — open core's own authority for
  resolving the posture. Its header said both walled postures require the
  *enterprise* package and that multi-org operation is *a commercial
  capability*; `isolationActive` argued "ENABLING a multi-organization posture
  is an entitlement". Rewritten around ADR-0132 D2/D3/D4: the mechanism (both
  postures probe the same registrar) is unchanged, the answer to "can an open
  install run isolated?" is now yes, and the `probeEntitledPostures` seam is
  explained as existing for the commercial subclass rather than as a tier.
- `cli/src/commands/serve.ts` — the mount block no longer says the runtime
  "lives in the closed-source package", and the operator-facing install remedy
  no longer ends "this runtime is closed-source and is NOT on the public npm
  registry ... Without one this bullet is not followable". That sentence told
  an open-source operator the remedy they had just been given was unusable.
- `cli/src/utils/tenancy-posture-hints.ts` — the `isolated` hint both
  `os serve` and `os doctor` render. Not in the card's count: its regex cannot
  see `the enterprise ${ORGANIZATIONS_RUNTIME_PKG}`.
- `plugin-dev/src/dev-plugin.ts` — two more operator-facing strings (the
  degraded-tenancy warning and the stage-2 mount refusal).

Because one name now denotes two packages (ADR-0132 D3), every rewritten site
names which one it means; where a site was load-bearing on ADR-0105 D12 the
citation is repointed at D12-as-amended rather than deleted.

Pin tests updated deliberately, with the reason recorded in each file:
`serve-organizations-message-spelling.test.ts` (5 hits) and
`doctor-organizations-message-spelling.test.ts`. The pins were not relaxed —
they still compare whole rendered lines built from
`Serve.ORGANIZATIONS_RUNTIME_PKG`, and both CONTROL blocks still differ from
their expectation only in spacing. The doctor roster assertion still pins
`edition: 'enterprise'`, which stays true: the roster records the ENTITLED
variant.

Left alone on purpose, and why, in the PR body: 2 hits that describe the
genuinely commercial same-named package, and 15 that state the package is
absent/not installable in this workspace as the reason a fixture fakes or
skips a proof — the live capability question the card dispatches as
report-only.

Refs ADR-0132, ADR-0105 D12 (as amended), #16130, #16137, PR #16215.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…he card's pattern could not see (#16718)

The card's measurement is a case-sensitive regex over
`enterprise (@objectstack/organizations|multi-org|organizations)` plus two
`closed-source`/`cloud-private` forms. Re-running it case-INSENSITIVELY, and
widening to the neighbouring `cloud-private` / `enterprise package` spellings
inside the two files the card names as its worst sites, turns up nine more
population-1 statements. All nine are in files the previous commit already
corrects; every one is a comment.

- `cli/src/commands/serve.ts` (×5)
  · the host-importer docblock used `@objectstack/organizations` as THE example
    of "a cloud-private package the framework cannot see" — the same reasoning
    #16539 already applied to the dogfood probe: it is no longer a name this
    repo can use as an example of something a host root does not have. The
    example is now framed on what actually decides resolution (the host app's
    declaration), with ADR-0132's two-packages fact named.
  · the cloud#1013 narrative said the load "never found the cloud-private
    package" — true of 2026-07, asserted in the present tense. Now
    "then-cloud-private": the history is kept, the present claim is dropped.
  · the live Stage-1 mount comment said "This package is cloud-private" in the
    boot path itself. Now APP-DECLARED, citing ADR-0132 D3 — which build of the
    name a deployment gets is the host manifest's call, not `serve`'s.
  · the ADR-0093 D5 fail-fast comment said "the enterprise package can't
    provide tenant isolation".
- `plugin-dev/src/dev-plugin.ts` — `ENTERPRISE @objectstack/organizations`, in
  capitals. The card's pattern is case-sensitive, so this hit was invisible to
  the 65-occurrence count while sitting two lines above one that was counted.
- `plugin-auth/src/auth-plugin.ts` — "so the enterprise package needs no change
  to light it up", on the `org-scoping` presence probe.
- `qa/dogfood/test/enterprise-organizations.ts` — the doc heading on
  `ORGANIZATIONS_PKG` still opened "The enterprise multi-org package", directly
  contradicted by the ⚠️ #16539 note two lines below it.
- `qa/dogfood/test/rls-multitenant.dogfood.test.ts` — "moved to the ENTERPRISE
  package ... not part of this open workspace". Both halves false since #16215.
  The skip is unchanged and its real reason is now stated: the gate needs the
  runtime resolvable FROM THE APP UNDER TEST, which is what
  `organizationsAvailable` probes.

⛔ No behaviour change: every hunk is a comment. Populations 2 and 3 are
untouched — see the PR body for the per-site accounting.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…erprise-only (#16718)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 12 package(s): @objectstack/cli, @objectstack/core, @objectstack/platform-objects, @objectstack/plugin-auth, @objectstack/plugin-dev, @objectstack/plugin-security, @objectstack/plugin-sharing, @objectstack/runtime, @objectstack/service-datasource, @objectstack/service-settings, @objectstack/service-storage, @objectstack/types, touching 13 documentable anchor(s). ⚠️ 8 changed file(s) yielded no anchor (packages/core/src/security/api-key.ts, packages/plugins/plugin-security/README.md, packages/plugins/plugin-sharing/src/sharing-plugin.ts, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

26 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 b11bfb9a8a130deef53f0336083ce7ad47900fb9.

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

What this run could not see
  • 8 changed file(s) yielded no anchor (packages/core/src/security/api-key.ts, packages/plugins/plugin-security/README.md, packages/plugins/plugin-sharing/src/sharing-plugin.ts, …) — pages documenting those are invisible to this run
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 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; 100 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.

Coarse fallback — 71 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 b11bfb9a8a130deef53f0336083ce7ad47900fb9packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3e0e00eee1b4c598bccf4416190749c0cdd23743 — the merge of head 076f1d54ebc4a7d51fb920ebc9157b394c68a751 into base b11bfb9a8a130deef53f0336083ce7ad47900fb9, 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 3e0e00eee1b4c598bccf4416190749c0cdd23743 && git checkout 3e0e00eee1b4c598bccf4416190749c0cdd23743
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b11bfb9a8a130deef53f0336083ce7ad47900fb9 076f1d54ebc4a7d51fb920ebc9157b394c68a751 && git checkout -B drift-repro b11bfb9a8a130deef53f0336083ce7ad47900fb9 && git merge --no-ff 076f1d54ebc4a7d51fb920ebc9157b394c68a751

node scripts/docs-audit/affected-docs.mjs --json b11bfb9a8a130deef53f0336083ce7ad47900fb9

⚠️ 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 b11bfb9a8a130deef53f0336083ce7ad47900fb9 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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

1 participant