docs(organizations): state ADR-0132's entitlement boundary as the reason the cross-tenant proofs stand in - #17910
Conversation
… cross-tenant proofs stand in
ADR-0132 brought the multi-organization runtime back to open core
(Apache-2.0, published on npm), which falsified the reason thirteen files
gave for standing in for it: "the enterprise package is cloud-private /
not installable in this workspace".
The EFFECT those files describe is unchanged and every behaviour, every
dependency and the pin stay exactly as they were. Only the stated cause
moves, to the true one: ADR-0132's entitlement boundary forbids any
framework package DECLARING `@objectstack/organizations`, pinned by
`no-framework-dependents.pin.test.ts` ("Apps declare it; packages do
not"), so a framework package cannot depend on it and cannot resolve it;
the proof that the real plugin walls tenants lives in cloud's
`security-enterprise` multi-org integration test.
Sites were enumerated by subject + cause with context
(`git grep -nE -C2 "cloud-private|not installable|closed-source"`) and
classified per site; only the class that still stated the stale reason as
currently true was edited. Measured while classifying: a bare ESM
`import()` of the package from a framework package answers
ERR_MODULE_NOT_FOUND "Cannot find package" with its `dist` built and
unbuilt alike, so the plugin-dev fail-fast signal is not a function of
build state.
Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c
Co-authored-by: Claude <noreply@anthropic.com>
…e correction `packages/verify/src/harness.ts` is the one touched file inside a published `files[]` path whose bytes move: its TSDoc is carried into `dist/index.d.ts` (measured after a real build, with a positive control and a stale-cache probe). The other touched published packages — cli, plugin-dev, types — changed only `*.test.ts` files, which the shared tsup entry (`src/index.ts`) never reaches. Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 2 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 2 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 59d2f7ffd95eaf30920d9e5e4284587f55b74db7 && git checkout 59d2f7ffd95eaf30920d9e5e4284587f55b74db7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a9c64779046facc1b0b2e74f71a9441b0f5b7fb8 7990da9233af387b15acd8c5e8e748855087d450 && git checkout -B drift-repro a9c64779046facc1b0b2e74f71a9441b0f5b7fb8 && git merge --no-ff 7990da9233af387b15acd8c5e8e748855087d450
node scripts/docs-audit/affected-docs.mjs --json a9c64779046facc1b0b2e74f71a9441b0f5b7fb8
|
Contract review — PR #17910 (card #17369)Head reviewed:
① Derived judgments — measured here, ⛔ not ratified from the report1. The acceptance is met in the form triage rewrote it: class (i) → zero, ⛔ not a phrase → zero. Every class-(i) site now states ADR-0132's entitlement boundary as the reason, and ⭐ the EFFECT survived where it had to: the three 2. ⭐ A class-(i) site that NOBODY named, and it is the most customer-visible of the whole set. 3. Two survivors this seat DOUBTED, and reading settled both against the doubt. 4. ⛔ Every banned path is clean, measured on the delivered head: 5. Clause-② re-measured on this head (twice tonight, the second time after the line was clobbered): added exported declarations in ② ⛔ Two figures in the report that measurement CONTRADICTS — both named, neither a defecta. 「Still 5/5 after the change, deliberately」 is WRONG. Measured: b. The ③ Boundary flagsa. b. The judgement call the dev flagged as reasonably-other-way — this seat read it and agrees, without overturning the flag. Five cloud-measurement sites ( c. ⭐ A ruling's line numbers went stale mid-card and the dev located from TEXT. Triage excluded d. #17911 was filed BARE and UNASSIGNED, explicitly ungraded — no e. The clobber was this seat's fault, and the dev's diagnosis is better than the one this seat published. f. ③ is met: 35 check names, all terminal — 31 success / 4 skipped / zero failures; Independence pairTier: default judgment — 「余席条款②复核 = 默认判断档自审加门禁」. VerdictPASS. ⛔ No carrier to clear — the claim declared the axis Generated by Claude Code |
Fixes #17369
Clause-②: no
Option 3, as ruled (director batch #114 item 3, carried by the maintainer's 「其他同意」 at
5629544916): the prose moves, the behaviour does not. Zero dependency changes, zero behaviour changes,no-framework-dependents.pin.test.tsuntouched and green.Acceptance is triage's rewritten one (
5649534177§③), not the ruling's execution line: class (i) reaches zero, never "a phrase reaches zero".That grep still reads 5 hits / 5 files, and it is supposed to.
The enumeration — method, then membership
Subject + cause, with context, exactly as triage prescribed after breaking the single-line form on this very card:
On the merge base
a9c64779046facc1b0b2e74f71a9441b0f5b7fb8(non-shallow): 60 hits / 36 files, minus publishedCHANGELOG.mdand.changeset/**⇒ 47 hits / 30 files. Every hit was then read in context and classified by hand — ⛔ no single-line| grep -i organizationsfilter, which is what hides a site whose subject and cause sit on different lines (harness.posture.test.tsis one: the subject is on line 9, the cause on line 10).Membership proof against the claim's 30-file set (
5649601883) — ⛔ not a count agreeing with a count:The two sets are equal by membership, and the intersection is non-empty, so the comparison instrument is live rather than silently matching nothing.
Per-site classification — all 47 in-scope hits
(i) still states the stale reason as currently true = MUST-FIX · (ii) already past tense / already recorded as "no longer cloud-private" = not touched · (iii) the subject is another package = not touched
Counts: (i) 21 hits / 13 files — all fixed · (ii) 19 hits / 13 files · (iii) 7 hits / 5 files.
Class (i) — fixed (21 hits, 13 files)
packages/verify/src/harness.ts:45@objectstack/organizationsruntime"packages/verify/src/harness.ts:159BootOptions.multiTenantTSDocpackages/verify/src/harness.ts:513node_modules, so the import could never succeed"sopackages/verify/src/harness.ts:534packages/verify/src/harness.posture.test.ts:10so— the card's headline sitepackages/verify/src/harness.posture-only.test.ts:4packages/verify/src/harness.posture-only.test.ts:12sopackages/verify/src/harness.host-resolution.test.ts:9node_modules, so the import could never succeed"sopackages/verify/src/harness.host-resolution.test.ts:24packages/cli/test/serve-organizations-host-resolution.e2e.test.ts:9node_modules, so the import could never succeed"sopackages/cli/test/serve-organizations-host-resolution.e2e.test.ts:24packages/types/src/node.test.ts:10node_modules. It could therefore never resolve"packages/qa/dogfood/test/automation-toggle-tenant-scope.dogfood.test.ts:41packages/qa/dogfood/test/showcase-external-autoconnect.dogfood.test.ts:78multiTenant: truewith the cloud-private@objectstack/organizations"packages/plugins/plugin-dev/src/dev-plugin-tenancy-failfast.test.ts:22so, and "absent from this workspace" is falsepackages/plugins/plugin-dev/src/dev-plugin-tenancy-mount-refusal.test.ts:23packages/plugins/plugin-dev/src/dev-plugin-tenancy-posture.test.ts:23sodocs/qa/platform-checklist/areas/attachments-storage.json:189@objectstack/organizationsis cloud-private, so the dogfood matrix's cross-tenant block is gated behinddescribe.skipIf(...)"sodocs/qa/platform-checklist/areas/attachments-storage.json:277docs/qa/platform-checklist/areas/attachments-storage.json:358content/docs/plugins/packages.mdx:331⭐ The
attachments-storage.jsoncluster is the one the order singled out, and the effect survives verbatim. The three copies carry the same MARKER string; only the cause clause changed. The sentences that carry the effect — "skips BY DESIGN here. That isblocked(dependency), never a pass and never a defect. A run that is supposed to ship the package setsOS_TEST_MULTI_ORG_ENABLED=1, which turns the skip into a hard failure instead of a silent one." — are byte-identical to before, and all three copies stay identical to each other (verified: 3 occurrences before, 3 after, and the file still parses as JSON).Class (ii) — already past tense or already self-correcting (19 hits, not touched)
packages/cli/src/commands/serve.ts:1107:820; ona9c64779it sits at:1107— the only §1/§3 figure that movedpackages/rest/src/rest-api-plugin.ts:300packages/rest/src/single-kernel-isolated-api-key-matrix.test.ts:21apps/objectos-ee"packages/rest/src/single-kernel-isolated-session-org-claim-matrix.test.ts:11objectstack serveof cloud'sapps/objectos-ee"packages/core/src/security/resolve-authz-context.ts:483isolatedboot")packages/core/src/security/resolve-authz-context.test.ts:1604packages/qa/dogfood/test/enterprise-organizations.ts:26node_modules"packages/qa/dogfood/test/enterprise-organizations.ts:59packages/qa/dogfood/test/enterprise-organizations.ts:101packages/qa/dogfood/test/enterprise-organizations.ts:152packages/qa/dogfood/test/enterprise-organizations.test.ts:7packages/qa/dogfood/test/enterprise-organizations.test.ts:20@objectstack/organizationswas cloud-private"packages/qa/dogfood/test/enterprise-organizations.test.ts:53packages/types/src/node.test.ts:50packages/plugins/organizations/src/open-only-wall-acceptance.test.ts:20packages/services/service-cluster/src/multi-node-gate-mount.ts:61@objectstack/organizationsis ONE NAME over TWO packages since ADR-0132"docs/adr/0132-…:35docs/adr/0132-…:218docs/adr/0105-…:357apps/objectos-ee, with the copy that app declares. ADR-0132 split one package name across two packages, and this repository says so in its own words atmulti-node-gate-mount.ts: "the framework publishes an Apache-2.0 package of that name and the commercial repo keeps a private licence-gated subclass of it". The copy those five sentences name is therefore still cloud-private, and the sentences are still true.serve.ts:1107— which triage excluded by name — is the same shape with an explicitthen-marker; these five are the same shape without one.Class (iii) — the subject is another package (7 hits, not touched)
content/docs/kernel/services-checklist.mdx:74@objectstack/service-ai— and true as written. This is the hit that makes the ruling's grep-to-zero a wrong editpackages/types/src/node.ts:34@objectstack/service-ai-studio, not to organizations. The claim it does make about organizations — host-supplied, invisible to a bare import — is still true (measured below)packages/types/src/node.test.ts:74packages/spec/src/kernel/platform-capabilities.ts:204@objectstack/packages in generalpackages/spec/src/kernel/platform-capabilities.ts:252@objectstack/security-enterprise(ADR-0057)packages/spec/src/api/error-code-ledger.zod.ts:43content/docs/references/api/error-code-ledger.mdx:47content/docs/references/is ⛔ never hand-edited⇒ The order's
packages/specreading is confirmed independently: neither spec hit names@objectstack/organizations, so nothing here routes to thedomain:specseat and no file underpackages/spec/**is touched.The two controls, printed
entitlement boundarynot installable in this galaxygit grep -nF "not installable"services-checklist.mdx:74is true)Class (i) after the change: 0. Remaining in-scope hits: 32 — every one of them class (ii) or (iii), including five new ones created by the corrections themselves, which say in so many words that being closed-source is not the reason.
The pin, and the boundary it defends
⛔ Zero dependency changes.
git grep -l '"@objectstack/organizations"' -- '*package.json'returns exactly one file before and after —packages/plugins/organizations/package.json, the package's own manifest.no-framework-dependents.pin.test.tsis not in this diff and is green.Measured while classifying — the
plugin-devpremise holds, and is not a function of build stateThree
plugin-devsites said the dynamic import fails because the package is "genuinely absent from this workspace". Since ADR-0132 it is a workspace member, so before rewriting the cause I had to know whether the effect is still true — the order's STOP rule covers exactly this ("if a class-(i) site cannot be made to tell the truth without a behaviour change — STOP and report"). It is true, and for the entitlement-boundary reason:import('@objectstack/organizations')frompackages/plugins/plugin-dev, run on this file's own vitest runner, answersERR_MODULE_NOT_FOUND"Cannot find package" — the package directory is never found.packages/plugins/organizations/diststubbed present: same answer,ERR_MODULE_NOT_FOUND"Cannot find package". So the signal is not a function of build state.require-shaped resolve with the pnpm bin shim'sNODE_PATHdid resolve, topackages/plugins/organizations/dist/index.js— so the hoisted store copy is genuinely reachable that way, and the instrument can say "found". Node's ESM resolver does not consultNODE_PATH, which is why the two differ.git status --porcelainon that path being empty; the throwaway probe test was deleted before the first commit.⇒ No behaviour change is needed anywhere in class (i), so the STOP rule did not fire. That measurement is recorded in
dev-plugin-tenancy-failfast.test.tsitself, because it is the reason that file may keep reading a real failure rather than a stubbed one — and it is the same build-state trap #16539 had to move arequire-shaped probe off a workspace name to escape.Changeset
The gate's own words, from
pr-automation.yml's "Require a changeset (or theskip-changesetlabel)" step:This PR is not tests-only, so route 2 does not apply and the label would be wrong. Measured rather than assumed:
packages/verify/src/harness.tsis source, its TSDoc is carried intodist/index.d.ts, anddistis in that package'sfiles[]. After a real build of@objectstack/verifyand its closure, the corrected sentence is present inpackages/verify/dist/index.d.ts, with a positive control (untouched TSDoc prose from the same block: HIT) and a stale-cache probe (cloud-privatein that.d.ts: absent, so the artifact is fresh rather than a turbo cache hit).grepfor a phrase the formatter had wrapped across two lines. A zero against a dead instrument is not a reading; the figures above come from the whitespace-normalised re-measurement.⇒ Route 1,
patch,@objectstack/verifyonly. The other three touched published packages (cli,plugin-dev,types) changed only*.test.tsfiles, which the shared tsup entry (entry: ['src/index.ts']) never reaches and which CI's "No compiled test files in any dist" step forbids indistanyway;packages/qa/dogfoodisprivate: true. ⛔ No package is named whose published bytes do not move, and no level is raised: no behaviour and no public surface moves, sopatch.Verification
Everything below was run on this branch. Exit codes captured before any pipe; every heavy run went through
scripts/pm/os-verify-lock.sh(slotissue-17369-cli-seat).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstacknode scripts/pm/dispatch-gates.mjs --ran … --repo …92 derived, 92 run, 0 NOT-MEASURED, 0 UNRUN— a derived zero: every family carries a recorded exit code and none is 3pnpm linteslint . --no-inline-config)pnpm --filter @objectstack/organizations testno-framework-dependents.pin.test.tsgreen, and not in this diffpnpm --filter @objectstack/organizations typechecktest·typechecktest·typecheckpnpm --filter @objectstack/verify typecheckOS_TEST_TIERS=nightly … vitest run test/serve-organizations-host-resolution.e2e.test.ts… vitest run test/automation-toggle-tenant-scope.dogfood.test.ts test/showcase-external-autoconnect.dogfood.test.tsturbo run build --filter='./packages/*' --filter='./packages/*/*'Three gates first read non-zero and are recorded twice on purpose, because a prerequisite failure is neither a pass nor a finding:
pnpm --filter @objectstack/spec run check:skill-examplespackages/client-react/distholds no.d.tsdeclarations — the package is not built"pnpm check:dual-build-cjs-loadspnpm buildfirst. ⛔ This is NOT a pass: nothing was measured."pnpm check:type-check-debt--max-old-space-sizecap, which the gate names as the binding constraintpnpm --filter @objectstack/cli exec vitest run --project integration …returned exit 1 reading "No test files found" — recorded as NOT MEASURED, ⛔ not as a red. I had assumed "spawns the CLI ⇒ integration tier" and that was wrong twice over:packages/cli/vitest-tiers.tsclassifies*.e2e.test.*as a nightly tier, so withOS_TEST_TIERSunset the file is outside both projects' population by design. The passing reading in the table above is the one taken under the switch the file actually runs in.test/vitest-tiers-partition.test.tsis green (22 passed), so the partition itself is intact.⛔ One pre-existing failure in
packages/verify, proven not to be this diff — filed as #17911pnpm --filter @objectstack/verify testreadsTests 1 failed | 102 passed (103). The failure isharness.host-resolution.test.ts→ "CONTROL — thedeclared-unresolvableremedy is unchanged: declared, not installed (#4719)", and it is #16539's defect in a file that card's fix did not reach:origin/mainblobc89a1c1b1and re-run — same single failure, same case. Restore proven bygit diff HEADempty,git status --porcelainempty, and the blob back to HEAD'se4ea9ed7e.packages/plugins/organizations/distpresent →1 failed | 6 passed (7); the same directory moved aside →7 passed (7). So the control's verdict is a function of whether a sibling package has been built.⇒ Not fixed here — it is a behaviour change to a test fixture, which this prose-only card may not make. Filed unassigned and bare as #17911.
Acceptance notes
Measured in passing, in scope for nothing here:
packages/plugins/plugin-dev/src/dev-plugin-tenancy-failfast.test.ts:35-37said@objectstack/organizations"is really absent" — the same falsified premise as the class-(i) hit eleven lines above it, but carrying none of the three enumerated phrases, so the ruled instrument cannot see it. Corrected in place to "it really does fail to resolve from here", because leaving it would have made the file contradict its own corrected paragraph. ⛔ Same file, same sentence, same defect — no new behaviour and no new verification surface.content/docs/plugins/packages.mdx:329still heads the section### @objectstack/organizations (enterprise). "enterprise" reads as a product tier rather than a licensing claim, and the heading is a doc anchor other pages may link to, so it is left alone. noted, not filed: renaming it is a docs decision, not a defect.packages/plugins/plugin-dev/src/dev-plugin-tenancy-posture.test.ts:29says a successfully mounted plugin is "unobservable in open-source CI". That is still true forplugin-dev(it may not declare the package), so it is not a class-(i) site — but the phrase "open-source CI" now carries the old framing. noted, not filed: no false statement, and no PR or person is queued to touch this file.packages/spec/**and nodocs/adr/**file is touched, and both readings that would have forced one were checked independently rather than inherited: the two spec hits name other subjects, and all three ADR hits are history.packages/verify/src/harness.host-resolution.test.ts— filed as verify: the harness.host-resolutiondeclared-unresolvableCONTROL flips red once packages/plugins/organizations is BUILT — #16539's defect in a file its fix did not reach #17911 after a targeted duplicate search (REST/searchis 403 for this seat by design, so one targeted MCPsearch_issues, declared here as the channel switch; it returned 2 results, so the instrument fired rather than being silently empty). dogfood: the enterprise-organizationsdeclared-unresolvableCONTROL flips red once packages/plugins/organizations is BUILT — its premise died when the package moved to open core #16539 is the same defect inpackages/qa/dogfoodand is closed; types: thenode.test.tshost-only-package pin is green in CI only whilepackages/plugins/organizations/distis absent on the shard that runs it — #16215 made its example package a workspace member #16552 applied the same remedy topackages/types.packages/verifywas never covered.Generated by Claude Code