Repository navigation
Closed
Labels
area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedpm:dispatchedDispatched to a dev agent by /pm-dispatchDispatched to a dev agent by /pm-dispatchpm:epicReserved by a dedicated epic PM, parent or sub-issue; other PMs never take it; never with pm:queueReserved by a dedicated epic PM, parent or sub-issue; other PMs never take it; never with pm:queuepriority:p1High: required for production / M2High: required for production / M2security
Description
Filing gate ③: owed scope of #15212, found by the epic seat's reconciliation against main at 2919ddb875. ADR-0131 D8 says no layer has a NULL arm. The D8 note (6325596117) records only the storage organizationOutOfWriteReach as the remaining organization-less answer. Step 7 did not touch the settings service.
- Who acts: the epic seat dispatches it.
- Dedupe: none ("settings organization-less sys_setting fallback tenant rung" → no carrier).
Part of #15212.
What is on main
packages/services/service-settings/src/settings-service.ts:
OrganizationReach(:150-170): for a caller that names an organization (own), the tenant and user rungs read that organization's rows plus the rows written with no organization, which act as a fallback for every organization.- The
organization-lessandunwalledkinds read organization-less rows too. - More readers:
:1822-1833,:2935-2940and:2984-2994.
The global rung moved to its own deployment-level sibling in step 2 (#24590). So an organization-less tenant- or user-rung row is a D8 NULL reading, unless a ruling keeps it.
Wanted
- Measure first.
- Can such a row still be written on any composition this release serves? Check fresh
single, walled, an 18.0.0-born store (nullable columns, the ceremony voluntary), and lean embedding. - Which callers read through
unwalled(the email brand name, the boot-time locale)? - If a ruling or ADR passage keeps the fallback, stop and report it with the citation.
- Can such a row still be written on any composition this release serves? Check fresh
- If owed:
- Retire the organization-less reading on the tenant and user rungs.
- Process-wide readers read the global sibling or name the one organization.
- The ceremony's inventory covers any existing organization-less
sys_settingrows (checkorganization-ownership-inventory.ts).
Clause-②: measured by the dev. A setting that stops answering for some caller is a narrowing.
Acceptance
- No tenant- or user-rung read admits a NULL-organization row. This is pinned for
own, walled andsingle. - An 18.0.0-born store keeps booting, and its settings keep answering (the upgrade harness lane).
- R1–R4 hold.
Metadata
Metadata
Assignees
Labels
area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedpm:dispatchedDispatched to a dev agent by /pm-dispatchDispatched to a dev agent by /pm-dispatchpm:epicReserved by a dedicated epic PM, parent or sub-issue; other PMs never take it; never with pm:queueReserved by a dedicated epic PM, parent or sub-issue; other PMs never take it; never with pm:queuepriority:p1High: required for production / M2High: required for production / M2security
Activity
objectstack-fleet commentedon Oct 22, 2026
Claim: epic seat round · 2026-10-22T11:37Z · owed scope of #15212, found by the reconciliation note 6339761793 (ADR-0131 D8; the D8 note 6325596117 names only storage's
organizationOutOfWriteReachas remaining)Session:
session_01Y5583bs2RDd53MH6kM1ca3Account:
os-steveBranch:
claude/issue-25202-settings-no-organization-less-rungWorktree: the cloud session's own checkout (
mode:cloud)Domain:
domain:servicesSeat:
epic:#15194File surface:
packages/services/service-settings/src/**(OrganizationReachand its readers), and the callers that read through theunwalledkind, if they must name an organization or read the global sibling.packages/cli/src/utils/organization-ownership-inventory.ts, only if existing organization-lesssys_settingtenant- or user-rung rows need a fate. If touched, the PR takesneeds:upgrade-harness..changeset/*.md.docs/adr/**.ORGANIZATION_OWNERSHIP_SHAPE_GENERATIONchange.Container & model:
M,mode:cloud,model: TIER_DEFAULT. The review is at the tier the diff's declared face picks.Clause-②: no (narrowing) — expected: a tenant- or user-rung read stops admitting an organization-less row; the dev measures first and re-declares from the diff
Responsibility: an organization-less
sys_settingtenant- or user-rung row answers every organization's read as its fallback (settings-service.ts:150-170) | none | reached by every settings read on a store that holds such a rowThread-read: none
Serial constraints cleared: no open PR on the line touches these files. refactor(objectql)!: retire the PLATFORM_OBJECT_TENANCY ledger (ADR-0131 D13) — the bypassTenantAudit mute becomes structural, and the [tenant-audit] warning stops telling a wall-exempt write to pass a tenantId #25201 (the objectql ledger) runs in parallel and shares no file.
objectstack-fleet commentedon Oct 22, 2026
os-dev-report
{
"issue": 25202,
"status": "in_progress",
"branch": "claude/issue-25202-settings-no-organization-less-rung",
"pr": "#25226",
"session": "session_01CUy2hdPMToZMsUtnAoshdP (dispatched by epic:#15194, session_01Y5583bs2RDd53MH6kM1ca3)",
"premise_still_valid": true,
"summary": "Measured OWED on main 9c431ba: no ruling keeps the tenant/user-rung organization-less fallback (ADR-0131 D1 as amended by 6328195425; D8; D8 note 6325596117 names only storage's organizationOutOfWriteReach). Built on head 576b828, draft PR #25226. Each reach now owns one organization: own(X) reads X only; a caller naming none reads/writes as the organization ADR-0131 D9 derives (resolveGuestOrganization, wired by SettingsServicePlugin), gets no tenant/user rung where none is derivable or behind a wall (tenant write refused), and a composition with no organization object keeps the lean NULL path (#25133 A). Same cause fixed: under single an organization-less second save inserted a duplicate row and was lost. Ceremony inventory unchanged (sys_setting already carries the attribution fate), so no needs:upgrade-harness. Clause-②: yes (narrowing). Metadata-protocol: none. CI pending.",
"measurement": {
"reach_admits (settings-service.ts:150-171, preferredRow :1822-1833, withinReach :2935-2940, loadScopedRows :2984-2994)": {
"own (caller names org X, every posture)": "query: each rung crossed with organization_id IN {X, NULL}. preferredRow: X's row, else the NULL row. A NULL row answers every organization that has no row of its own (the shared-owner fallback).",
"organization-less (no org, group/isolated)": "query: organization_id = NULL only. preferredRow: the NULL row. The caller reads exactly the organization-less rows.",
"unwalled (no org, single or no posture source)": "query: no organization filter at all. preferredRow: the first organization-stamped row, else the NULL row (candidates[0])."
},
"can an organization-less tenant/user row be written (probe through the real ObjectQL engine + wrapEngineAsSettingsEngine; driver stamps options.tenantId like injectTenantOnInsert)": {
"fresh single (sys_organization registered, one org)": "NO. setMany carries organization_id NULL. The engine's resolveSystemInsertOrganization derives the Default Organization, and the row lands stamped.",
"walled group / isolated": "NO. A tenant key is refused at the service pre-flight (SETTINGS_VALIDATION, constraint scope tenant). A user key passes the service and the engine refuses it: ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED (walled-posture). Rows: [] for both postures.",
"18.0.0-born store (nullable columns, voluntary ceremony)": "NO on a served composition: same write path as above. A nullable column matters only for the lean/--dev-without-auth path below. fixture-18.0.0/database.sql holds 2 sys_setting rows (localization.timezone and branding.workspace_name, both tenant, both org_mvgqf77lj6oc60fk), so 0 organization-less. fixture-18.0 also holds 2. The #24919 reading cited in the D1 amendment: 0 NULL organization rows on every store.",
"lean embedding (no sys_organization)": "YES, by ruling (#25133 A). The probe stored [[tenant,null],[user,null]]. A driver-created table (7b-0) refuses at NOT NULL; the in-memory driver and a pre-7b-0 nullable table accept.",
"--dev": "os dev composes auth, so it behaves as served single/walled. --dev with the auth tier off composes no organization object, so it takes the lean path (D9 note, boundary 3).",
"v17 store after the ceremony": "the inventory already gives sys_setting a fate (organization-ownership-inventory.ts:660, 'attribution', D10_SINGLE): single attributes to the Default Organization; walled leaves the row NULL and REPORTED. A reported row is the one population today's 'own' fallback serves on a served store."
},
"unwalled_write_defect": "Probe, single + sys_organization + one org, organization-less caller: set(motto,'one') stores [org_default,'one']. set(motto,'two') then probes upsertRow's identity with organization_id NULL, misses the stamped row and INSERTS a second one: [[org_default,'one'],[org_default,'two']]. Both readers keep answering 'one', so the write is lost. On a SQL store the identity index (ensureSysSettingIdentityIndex; fixture-18.0.0 line 895, COALESCE(organization_id,'global'),namespace,key,scope,COALESCE(user_id,'')) refuses the second insert, so the save fails. The cause is the same one: the unwalled caller never names the one organization.",
"callers reading through unwalled / organization-less": [
"plugin-auth auth-plugin.ts:1102 get(branding.workspace_name, {}): email appName. Tenant key. Branding is offered only where no wall stands (UNWALLED_ONLY_NAMESPACES), so this read happens under single only.",
"plugin-auth auth-plugin.ts:1155 get(localization.locale, {}): auth mail/SMS locale. Tenant key, read under EVERY posture. Under a wall it reads the organization-less rows only.",
"cloud-connection runtime-config-plugin.ts:1172 getMany(branding.*, {}): the anonymous runtime-config door. Single only (listManifests gate).",
"objectql lifecycle-service.ts:893-897 get(lifecycle.retention_overrides): a tenant key read without tenantId. The per-org read at :926 passes tenantId.",
"core resolve-authz-context.ts:2498/2510 resolveLocalizationContext({tenantId?, userId}): a signed-in user with no active organization (REST request context, MCP, current-user endpoints, plugin-audit getLocale).",
"the settings HTTP door: a signed-in caller with no active organization (verifiedContextFromRequest: tenantId from resolveRequestExecutionContext).",
"every other organization-less caller reads a GLOBAL namespace (auth, mail, sms, storage, membership policy), which the reach does not touch."
],
"what they read without the fallback": "Under single: the Default Organization's own row, once the reach NAMES the one organization. That is the row they already prefer today; nothing changes for them unless a NULL row is all a store holds, and the measured stores hold none. Under a wall: no tenant or user row, so the global rung (sys_platform_setting) or the default answers. Today they read only NULL rows, and a served walled store holds those only as ceremony-reported rows."
},
"tests": "576b8283: service-settings vitest 58 files / 982 tests pass; typecheck exit 0. New/rewritten pins: settings-organization-isolation.pin.test.ts (own, walled, single, lean; tenant + user rung; lock pre-flight), settings-organization-reach.pin.test.ts (real SettingsServicePlugin + ObjectKernel + ObjectQL: single with 1/0/2 orgs, group/isolated, lean). Upgrade harness upgrade-18x-harness.dogfood.test.ts 16/16 pass (both fixture legs + fresh store), incl. new 18.0.0 test: settings still answer via GET /api/settings/branding and for a reader naming no organization. Ablations from committed state: A1 own admits NULL -> 8 pins red; A2 plugin wires no organization source -> 4 plugin pins + the harness 18.0.0 settings test red; A3 write stores caller org not reach org -> 3 pins red. Downstream spot-check: plugin-auth 124, core 33, organizations 32 pass.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands at 576b828: 72 derived, 70 exit 0, 2 exit 3 NOT MEASURED (check-plugin-teardown-shape --self-test: shallow clone; check:dual-build-cjs-loads: partial build); --ran verdict ✓ 72 accounted. check-adr-0087-registration: ✓ not-required (no-migration-prescription). check-governed-merges --branch: 0/9 governed, 645 lines. R1 (fresh store boots and serves) and R2 via the harness; R4 walled covered by pins, walled dogfood boot NOT MEASURED locally. PR check-runs: pending.",
"dogfood_battery": "NOT MEASURED locally beyond the upgrade harness (16/16); CI full battery pending",
"mcp_calls": "read only: issue_read x5 (#25202, #15212, #15194)",
"api_writes": "2: fleet-write pr_create (#25226, run 39787962628), this comment",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Landed · epic PM
session_01Y5583bs2RDd53MH6kM1ca3(os-steve) · 2026-10-22T16:00Z. PR #25226 merged intomainas279fdcf469. It closes this card under ADR-0131 D8 and D1 as amended.sys_settingrow.own(X)reads X alone. A caller with no organization reads the organization D9 derives undersingle, and nothing under a wall. Only lean embedding ([Decision] A production os serve that composes no organization object (no auth secret, or no auth) still writes ownerless rows under --no-server: refuse that boot too, or refuse such writes in the engine everywhere #25133, letter A) still reads NULL rows.singleduplicated the row.CONTRACT_REVIEW_TIER, narrowing face, from an isolated reviewer: PASS 6343868210. Seat ACCEPT 6344522520.os serveunderisolatedandgroupwas measured before landing (health 200, settings read and write, no NULL row).sys_settingwith no organization filter) and fix(service-settings): a host that binds SettingsService without an organization source reads as the lean composition (NULL rows) on a store that registers sys_organization — derive from the bound engine, or refuse #25249 (a self-binding host with no organization source).