Skip to content

fix(service-settings,cloud-connection)!: Branding settings drive runtime-config under single; theme_mode and support_email retire; namespace withheld under walled postures - #24959

Merged
objectstack-fleet[bot] merged 14 commits into
mainfrom
claude/issue-24766-branding-runtime-config
Oct 20, 2026
Merged

objectstack-fleet[bot] merged 14 commits into
mainfrom
claude/issue-24766-branding-runtime-config

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #24766

Clause-②: no (narrowing)

What this implements

The maintainer's ruling B′ (6293793948) with its amendment (6293825075), under the standing rule 「甲」 (#6015 6292005854).

Under single: runtime-config's branding block reads the Branding settings, per request. RuntimeConfigPlugin (packages/cloud-connection) reads the branding namespace on every GET /api/v1/runtime/config (and the legacy alias), so a save shows on the console's and the sign-in page's next load:

served field settings key fallback when nobody set the key
productName branding.workspace_name host option, then OS_PRODUCT_NAME, then ObjectOS
logoUrl branding.logo_url host option, then OS_LOGO_URL
brandColor branding.accent_color host option, then OS_BRAND_COLOR
  • "Set" means any source but default: saved in Setup, or pinned by the settings layer's own OS_BRANDING_* variable. That is the same rule plugin-auth already applies to workspace_name, so the two readers of that key agree. The manifest default (ObjectStack) is never served on this door.
  • Precedence decision, stated because the ruling names only the env vars: the host's constructor option sits with the env var as the deployment default, so a saved value outranks both. Pinned.
  • productShortName, pwaDescription and pwaThemeColor follow the served name and colour unless configured themselves. Before, they were derived once at construction from the deployment values.
  • brandColor keeps its one meaning: the console's browser and installed-app theme colour (objectui getPwaThemeColor), beside each app's own AppBranding colours. No precedence rule is added.
  • A settings read that fails serves the deployment branding and logs one warn per run of failures. The door never answers 500 for it.
  • The served branding block keeps its shape. No exported type or option changed: RuntimeConfigPluginConfig gained no member (JSDoc only).

theme_mode and support_email retire, manifest version 1 → 2, in #21970's shape. branding.manifest.ts, the four translation bundles and the README follow. accent_color also loses its #6366f1 default: the page showed it as a colour the console never used, since an unset key is now served as the deployment's colour.

workspace_name's auth readers keep working. plugin-auth still reads the key for sign-in mail, the authenticator issuer and MCP sign-in; its binding now runs only where the namespace is offered (see the patch-round section below). The key's description states both effects: "The product name in the console, on the sign-in page and in the browser title, and in sign-in emails, the authenticator app and MCP sign-in."

Under group / isolated the server does not serve the branding namespace. SettingsServicePlugin holds a manifest in UNWALLED_ONLY_NAMESPACES (today only branding) back at init(). It registers that manifest from its kernel:ready hook only when the posture in force puts up no organization wall. It reads the posture the way its own door and service already read it (resolveAdmissionTenancyPosture). It decides at kernel:ready, not at init(): during init() the tenancy service may not be registered yet, and isolated is in force only once org-scoping has registered. The registry's own answer then covers every door:

  • GET /api/settings does not list branding;
  • GET and PUT /api/settings/branding are refused 404 UNKNOWN_NAMESPACE by the existing unknown-namespace path, never a silent 200;
  • the runtime-config door reads the namespace only when the settings service lists it. Absent, it reads nothing, so no tenant data reaches that anonymous door, and the deployment's OS_* branding is served as before. No organization is resolved before sign-in.

A posture that cannot be read withholds the namespace and logs one warn naming it: offering is the one direction a guess must not take. No tenancy service, or a host with no async registry, offers it, which is how every lean kernel has always read the posture. settings-service.ts (claimed by epic #15194) is not touched.

Walled deployments, the auth product name. With branding withheld, OS_BRANDING_WORKSPACE_NAME, and an organization-less branding.workspace_name row, no longer name sign-in mail, the authenticator issuer or MCP sign-in; the OS_APP_NAME chain does. Operators move the value to OS_APP_NAME. This is stated in the service-settings and plugin-auth changesets.

The retired-key refusal under 「甲」: confirmed

A save naming a retired key gets the loud refusal through the version step: PUT /api/settings/branding naming theme_mode or support_email answers 400 UNKNOWN_KEY with details: { namespace: 'branding', key }, and the refusal covers the batch (a logo_url sent beside it does not land). Pinned in settings-routes.test.ts with stored rows seeded, and ablation-verified below. So the fallback (a warning in 18.x, with the refusal filed for 19.0) is not needed.

Clause-② measured

no (narrowing). Published types: @objectstack/service-settings exports the same symbols with the same types; only the value of brandingSettingsManifest changed. SettingsServicePluginOptions and RuntimeConfigPluginConfig changed in JSDoc only, and RuntimeConfigPlugin changed only in private members. The served runtime/config shape is unchanged. Narrowed accept sets, three:

  • branding.theme_mode / branding.support_email on every posture (now 400 UNKNOWN_KEY);
  • under walled postures, the whole branding namespace (now 404 UNKNOWN_NAMESPACE);
  • under walled postures, the OS_BRANDING_WORKSPACE_NAME override and any organization-less workspace_name row, which no longer name the auth product name.

The ruling's "wiring the three keys is a widening" is the capability sense, which is why @objectstack/cloud-connection takes a minor. No published surface widens in the Clause-② sense.

Cross-lane in this PR: packages/qa/dogfood (domain:cli) for the fixture re-point, and content/docs/ui/setup-app.mdx (domain:devx) for the Configuration row. content/docs/releases/ is untouched.

The Setup entry and the auth binding (patch round 1)

  • The Setup → Branding entry moves with the capability. SettingsServicePlugin contributes nav_settings_branding through the manifest service from the kernel:ready step that offers the branding namespace, and only then. It sits in Configuration at priority 110, after platform-objects' entries and before service-messaging's; the pattern is @objectstack/mcp's connect-ui.ts (cloud ADR-0009). SETUP_NAV_CONTRIBUTIONS drops the static line. check-app-nav-i18n.mjs boots the plugin through kernel:ready in a new roster entry, so the id stays in that gate's population: setup still 53 merged ids. Labels stay keyed apps.setup.navigation.nav_settings_branding.
  • plugin-auth binds branding.workspace_name only where the settings service lists the branding namespace, the runtime-config door's test. A walled boot no longer logs Auth: failed to apply branding.workspace_name: No settings manifest registered for namespace 'branding'., and the configured app name stays in force.
  • packages/cli/test/connect-agent-both-halves-wire.pin.test.ts counts 31 Setup ids, exactly: that harness composes SETUP_NAV_CONTRIBUTIONS and the MCP bundle only, and the Branding entry now travels with service-settings. It also asserts nav_settings_branding is absent there.
  • Changesets are now four: @objectstack/platform-objects minor (24766-setup-branding-entry) and @objectstack/plugin-auth minor (24766-auth-brand-binding) join the two above. Clause-②: no (narrowing) is unchanged for the PR.

Tests (head 0c71c4190, after merging origin/main 644e4b2c5)

  • pnpm --filter @objectstack/service-settings exec vitest run --maxWorkers=2: 56 files, 924 tests passed.
  • pnpm --filter @objectstack/cloud-connection exec vitest run --maxWorkers=2: 53 files, 625 tests passed.
  • typecheck for both (tsc --noEmit, plus -p tsconfig.test.json for cloud-connection): exit 0. --listFiles shows both programs compile every new and edited test file.
  • Importer spot-check (behaviour moved from init() to kernel:ready): plugin-auth's auth-settings-ordering.pin, auth-settings-seeded-boot.pin and dev-admin-seed-workspace-timezone tests, 3 files and 12 tests passed. plugin-auth aliases @objectstack/service-settings to source.
  • The dependency closures were built first (turbo run build --filter=@objectstack/cloud-connection... --filter=@objectstack/service-settings..., 34 tasks).

New pins:

  • service-settings/src/settings-branding-posture.pin.test.ts boots the real SettingsServicePlugin sequence with tenancy on a real ObjectKernel, and drives the real registerSettingsRoutes over the service it built:
    • under group and isolated: absent from the listing (the other namespaces are still listed); PUT and GET refused 404 UNKNOWN_NAMESPACE;
    • under single, with no tenancy service, and on a host with no async registry: listed, saved, read back;
    • a posture that cannot be read: withheld, one warning naming it.
  • service-settings/src/settings-routes.test.ts: the two retired keys. Not offered and not resolved, even with a stored row. PUT refused 400 UNKNOWN_KEY by name with the whole batch. A stored row never blocks a live save and is kept.
  • service-settings/src/manifests/branding.manifest.test.ts: version 2; exactly workspace_name / accent_color / logo_url in identity / appearance; no retired key; no default on accent_color / logo_url.
  • cloud-connection/src/runtime-config-branding-settings.test.ts:
    • under single: a saved logo URL is the one served, and an unset one falls back to OS_LOGO_URL; the same for the name (never the manifest default) and the colour;
    • the derived fields follow; a saved value outranks the host option; reads are per request; source: env counts as set; a failing read serves the env branding with one warning;
    • under group: the setting is not served and the door never calls getMany;
    • no settings service: unchanged.
  • settings-service.test.ts: the env 来源的 settings 值绕过 manifest 的 options 表校验 —— #5094 在写入 API 上堵住的洞,在 OS_* 覆盖这一侧原样敞开 #5204 env-override pins used branding.theme_mode as their example select. They now use localization.locale, also a domain-less select, and pin the same behaviour, as fix(service-settings)!: retire date_format, time_format, number_format and first_day_of_week from the Localization manifest (#21958) #21970 re-pointed its pins.

Ablations (each negative pin, put back, seen red, restored)

Each used scripts/ablation-replace.mjs in WRAP mode, with a literal anchor that must hit and restore proven by blob. Every restore ended "blob == HEAD and git diff HEAD is empty". The test subjects resolve by relative import to src, so no dist rebuild was involved.

  1. Posture gate removed (if (posture !== undefined && postureEnforcesWall(posture)) became if (false && …), blob a282064cb1d8 → 5e3180ac9016): 6 red in settings-branding-posture.pin.test.ts. They are listing / PUT / GET under group and isolated: expected [ 'company', 'localization', …(2) ] to not include 'branding' and expected 200 to be 404. Restored to a282064cb1d8.
  2. Door ignores the listing (the listManifests().find(…) replaced by a constant manifest, blob 1cedb39aa721 → 88ff9db94c1d): the walled-posture pin went red, expected { productName: 'Tenant Org', …(6) } to match object { productName: 'Env Product', …(2) }. Restored to 1cedb39aa721.
  3. Manifest default served as set (if (!entry || entry.source === 'default') continue; became if (!entry) continue;, blob → f429c41343c8): 3 red, expected 'ObjectStack' to be 'Env Product' (and … 'Host Product'). Restored to 1cedb39aa721.
  4. theme_mode put back into the manifest (blob 6e63f4cce79b → 85c21c5efa98): 7 red. The route pins answered expected 'SETTINGS_VALIDATION' to be 'UNKNOWN_KEY' and theme_mode must not be offered, the manifest pins failed, and the zh-CN / ja-JP / es-ES coverage reported key:theme_mode missing. Restored to 6e63f4cce79b.
  5. Read memoized at the first request (blob 1cedb39aa721 → bff4ef12d8d3): the per-request pin went red, expected 'Env Product' to be 'Renamed'. Restored to 1cedb39aa721.

Patch round 1 (head e6522a957)

  • service-settings: full suite 56 files, 929 passed; settings-branding-posture.pin re-run at e6522a957: 15 passed.
  • plugin-auth: full suite 173 files, 3319 passed, 10 skipped (before the test-only typing fix); auth-plugin.test.ts re-run at e6522a957: 112 passed; auth-settings-ordering.pin, auth-settings-seeded-boot.pin, dev-admin-seed-workspace-timezone passed.
  • platform-objects: full suite 82 files, 1232 passed.
  • cli unit project, the files that compose the Setup nav: 4 files, 70 tests passed (connect-agent-both-halves-wire.pin, platform-page-i18n-parity, adr-0048-app-split, format.server-ready-dev-admin-audience).
  • Typecheck exit 0 for service-settings, plugin-auth (with check:test-typecheck), platform-objects (with check:test-typecheck), cloud-connection and cli (with check:test-typecheck).
  • New and changed pins: settings-branding-posture.pin folds the real SETUP_APP + SETUP_NAV_CONTRIBUTIONS plus what the boot registered through the real SchemaRegistry: under group and isolated no nav_settings_branding in the merged Setup tree (controls nav_settings_company, nav_settings_localization present); under single, with no tenancy service, and with no async registry, exactly once in group_configuration, after nav_settings_feature_flags. auth-plugin.test.ts: walled pin (namespace not listed: no branding read, no subscription, no warn naming branding, setAppName never called) and a real-SettingsService pin (no branding manifest: unbound and silent; registered with a saved Acme Single: setAppName('Acme Single')).
  • Ablations (scripts/ablation-replace.mjs WRAP; every restore "blob == HEAD and git diff HEAD is empty"):
    • A. plugin-auth gate forced open: 2 red (the walled pin and the real-service pin, the latter showing the warn line). Restored to 9f496bd8c738.
    • B1. Static line put back into SETUP_NAV_CONTRIBUTIONS: 5 red (walled postures: to not include nav_settings_branding; the three offering postures: to have a length of 1 but got 2). Restored to ad98264e484d.
    • B2. Posture gate removed: 8 red (listing / PUT / GET / Setup entry under group and isolated). Restored to 171c958f5702.
    • C. The gate roster entry fires no kernel:ready: check-app-nav-i18n exits 1, "landed NO setup navigation id". Restored to 23f2ff594226.

Patch round 2 (head d616d4d87)

  • The dogfood re-point (contract review item 10):
    • packages/qa/dogfood/test/settings-organization-isolation.dogfood.test.ts now uses company.legal_name as its tenant fixture. Run for real under the isolated posture it boots (its guard leg asserts posture isolated, degraded false): 6 passed.
    • Control leg: the file restored from e6522a957 and run on the same code gives 4 failed, 2 passed. Three failures carry {"code":"UNKNOWN_NAMESPACE","message":"No settings manifest registered for namespace 'branding'."} (expected 404 to be 200 twice, expected 404 to be 400), and the data-API positive control fails expected false to be true. Restored: blob 531e8f552e34 == HEAD.
  • The no-wall branding suites: settings-config-change-audit, settings-audit-global-rung-sibling and audit-log-parent-read-gate, 3 files, 16 tests passed.
  • Typecheck, all exit 0: dogfood, plugin-auth (check:test-typecheck OK), service-settings, platform-objects (check:test-typecheck OK), cloud-connection.
  • No ablation this round: no mechanism file changed.

Gates

  • dispatch-gates --commands at the final head d616d4d87 derived 106 commands; the 29 new ones come from the docs page and the dogfood path. Each ran with its exit code captured before any pipe: 104 exited 0. check:skill-examples (spec) and check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: missing dists) and re-ran exit 0 after building their closures ("262 prose examples type-check across 3 surface(s)"; "112 published require entry point(s) across 69 package(s) load").
  • --ran with exit codes: "106 derived famil(ies) accounted for — 106 run, 0 NOT-MEASURED (a DERIVED zero — all 106 recorded an exit code and none of them is 3)".
  • The changeset gates at d616d4d87, each exit 0: check-adr-0087-registration --base origin/main ("1 declared-breaking changeset(s), each carrying an ADR-0087 disposition … not-required (no-migration-prescription)"), check-changeset-no-major, check-empty-changeset, and the adr-0087 --self-test (548 assertions).
  • Check Changeset was red on acaf3231b: a literal FROM/TO migration label tripped the ADR-0087 prescription detector. Reproduced locally (exit 1), reworded as the environment fix it is, and success on d616d4d87.
  • Earlier rounds, still standing: check:startup-registry-verdict, check:settings-bind-window and check:init-service-contract exit 0 at 0c71c4190; pnpm check:app-nav-i18n exit 0 at e6522a957 (setup: 53 merged nav ids).
  • ESLint over the dogfood test: exit 0. Control-byte scan of the round's files: no hit.

Acceptance notes (not filed)

  • docs/qa/platform-checklist/areas/platform-core.json, runtime-config-boot-read step 1 (logoUrl / brandColor only when configured by the plugin config or env vars): a saved Branding setting now serves them too, under single. Its namespace count is a single-posture count; under a walled posture branding is not served. QA text drift, not edited. Carrier: none.
  • packages/qa/dogfood/test/settings-organization-isolation.dogfood.test.ts uses company.legal_name as its tenant fixture, because branding is not offered under the isolated posture it boots.
  • packages/qa/dogfood/test/anonymous-door-sweep.classification.ts classifies runtime/config as "no tenant data". Under single the door now serves the one organization's saved branding, which the ruling defines as the deployment's. Under a wall it serves none. The wording still reads true by the ruling's definition. Carrier: none.
  • packages/platform-objects/src/apps/translations/setup-nav-dead-key-tombstone.test.ts keeps CONDITIONAL_SETUP_NAV_IDS = ['nav_sso_providers'], the ids whose labels must stay although no single composition contributes them. nav_settings_branding is now such an id (contributed only under single) and could join it. Carrier: none.
  • packages/qa/dogfood/test/platform-app-object-entry-views.test.ts says its roster mirrors check-app-nav-i18n's CONTRIBUTORS. The new service-settings entry contributes one url item, which that file (object-entry views) has nothing to judge, so its verdict is unchanged. Carrier: none.
  • On a multi-tenant host that resolves environments by hostname (env-registry), the branding read is the host kernel's own settings, process-wide, exactly as OS_PRODUCT_NAME was. Per-environment branding was never served here and still is not. Carrier: none.
  • workspace_name's manifest default ObjectStack still differs from the console's built-in ObjectOS. That is the two-defaults reading docs(deployment): the environment-variables page gives OS_PRODUCT_NAME a default the runtime does not use and omits six branding variables the runtime reads #24887 carries for the operator docs. Carrier: docs(deployment): the environment-variables page gives OS_PRODUCT_NAME a default the runtime does not use and omits six branding variables the runtime reads #24887.

Generated by Claude Code

@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests tooling labels Oct 20, 2026
@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/cloud-connection, @objectstack/platform-objects, @objectstack/plugin-auth, @objectstack/service-settings, touching 39 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/services/service-settings/README.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/environment-variables.mdx (via /api/v1/runtime/config (route, a path literal in a comment on a changed line))
  • content/docs/protocol/kernel/config-resolution.mdx (via /api/settings (route, a path literal in a comment on a changed line))
  • content/docs/protocol/kernel/error-handling.mdx (via /api/settings (route, a path literal in a comment on a changed line))
  • content/docs/protocol/kernel/index.mdx (via SettingsServicePlugin (symbol, a top-level class))
  • content/docs/ui/setup-app.mdx (via SETUP_NAV_CONTRIBUTIONS (symbol, a top-level const object), group_configuration (literal, a string literal in offerUnwalledOnlyNamespaces))

⛔ 2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v14.mdx (via /api/settings (route, a path literal in a comment on a changed line))
  • content/docs/releases/v17/17-7.mdx (via /api/settings (route, a path literal in a comment on a changed line))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/services/service-settings/README.md) — pages documenting those are invisible to this run
  • 6 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 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; 97 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 24 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 f32d795ce511c0644a0bc2b110710f8a181d87bb → packageMentionDocs.

Which tree this was computed on

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

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

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

REWORK (patch round 1) · #24766 · domain:services seat 2 · os-support-ai · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T18:46Z · head 0c71c4190, claim 6296927167 (file surface widened in this act), under the maintainer's ruling B′ (6293793948) and its amendment (6293825075).

The delivered half matches the ruling:

  • Under single, runtime-config reads branding.workspace_name / logo_url / accent_color per request. A key nobody set falls back to the deployment's own branding.
  • theme_mode and support_email retire through the version step, refused 400 UNKNOWN_KEY by name, so the 「甲」 fallback is not needed.
  • Under group / isolated, the namespace is registered at kernel:ready only where no wall stands: absent from the listing, 404 UNKNOWN_NAMESPACE on read and write, and the anonymous door reads nothing.
  • Five ablation legs went red as predicted.

Accepted deviations:

The seat's answers to the three open questions are its implementation calls, not product questions:

  1. The Setup entry moves with the capability (the dev's option A), in this PR.
    • SettingsServicePlugin contributes the one nav_settings_branding item from the kernel:ready step that already offers the namespace, the in-tree connect-ui pattern.
    • SETUP_NAV_CONTRIBUTIONS drops the line.
    • check-app-nav-i18n.mjs's contributor roster keeps the moved id in that gate's population.
    • Labels stay keyed apps.setup.navigation.nav_settings_branding.
    • Cross-lane, declared in the claim: packages/platform-objects/src/apps/setup-nav.contributions.ts (one line, domain:engine) and packages/cli/scripts/check-app-nav-i18n.mjs (one roster entry, domain:cli).
    • Pins: the entry under single, and its absence under group.
  2. The walled-boot warn goes (option A).
    • plugin-auth's branding.workspace_name binding is skipped when the settings service does not list the branding namespace, the same test the runtime-config door makes. A walled boot logs nothing about it.
    • Claim surface: packages/plugins/plugin-auth/src/auth-plugin.ts, the applyBrand binding only.
    • PR fix(plugin-auth): every in-process auth bridge hands handleRequest the request's transport #24931 (in the merge queue) edits another region of the same file; whichever lands second merges main.
    • Pin: a walled boot emits no warn for it, and single still applies a saved name.
  3. The first line. After this round the seat rewrites it to Fixes #24766, and the dev does not edit the body. The dev's out-of-scope notes stay notes; the QA checklist step wording goes to the Acceptance notes.

Before ACCEPT the seat owes a contract-review-tier review of the narrowing: the settings accept set, and the namespace withheld under a wall.


Generated by Claude Code

… the brand only where branding is offered

Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y
Co-authored-by: Claude <noreply@anthropic.com>
… (TS2345 in check:test-typecheck)

Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y
Co-authored-by: Claude <noreply@anthropic.com>
…osition (Branding now travels with service-settings)

Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e6522a9572fade10de53501faca4758190ae9b57
Local-runs: none
Scope: packages/cli/scripts/check-app-nav-i18n.mjs, packages/cli/test/connect-agent-both-halves-wire.pin.test.ts, packages/cloud-connection/src/runtime-config-branding-settings.test.ts, packages/cloud-connection/src/runtime-config-plugin.ts, packages/platform-objects/src/apps/setup-nav.contributions.ts, packages/plugins/plugin-auth/src/auth-plugin.test.ts, packages/plugins/plugin-auth/src/auth-plugin.ts, packages/services/service-settings/README.md, packages/services/service-settings/src/manifests/branding.manifest.test.ts, packages/services/service-settings/src/manifests/branding.manifest.ts, packages/services/service-settings/src/settings-branding-posture.pin.test.ts, packages/services/service-settings/src/settings-routes.test.ts, packages/services/service-settings/src/settings-service-plugin.ts, packages/services/service-settings/src/settings-service.test.ts, packages/services/service-settings/src/translations/en.ts, packages/services/service-settings/src/translations/es-ES.ts, packages/services/service-settings/src/translations/ja-JP.ts, packages/services/service-settings/src/translations/zh-CN.ts + packages/services/service-settings/src/index.ts, packages/services/service-settings/src/settings-service.ts, packages/services/service-settings/src/settings-routes.ts, packages/services/service-settings/src/settings-service.types.ts, packages/spec/src/security/tenancy-posture.ts, packages/spec/src/system/settings-manifest.zod.ts, packages/core/src/security/admission-tenancy-posture.test.ts, packages/core/src/hook-dispatch.ts, packages/core/src/kernel.ts, packages/plugins/plugin-auth/src/auth-settings-ordering.pin.test.ts, packages/objectql/src/registry.ts, packages/objectql/src/plugin.ts, packages/mcp/src/plugin.ts, packages/mcp/src/connect-ui.ts, packages/services/service-messaging/src/messaging-service-plugin.ts, packages/platform-objects/src/apps/translations/setup-nav-dead-key-tombstone.test.ts, packages/qa/dogfood/test/settings-organization-isolation.dogfood.test.ts, packages/qa/dogfood/test/audit-log-parent-read-gate.dogfood.test.ts, packages/qa/dogfood/test/anonymous-door-sweep.classification.ts, packages/qa/dogfood/test/platform-app-object-entry-views.test.ts, packages/verify/src/harness.ts, packages/verify/src/harness.posture-only.test.ts, packages/cli/src/utils/one-shot-settings.ts, scripts/check-settings-bind-window.mjs, .github/workflows/ci.yml, AGENTS.md

Read-only: the card (body and ten comments), the PR body, its file list, the net diff against main, the head's check-runs, and the cited code at the head through the object store. Nothing built, run or re-run. Every line number below is at the head sha unless it names a changeset or the card.

① Derived judgments

Named RIGHT, each against the committed tree:

  1. Retired keys refused loudly, by name, whole batch. setMany pre-flights every key of the patch before any write (settings-service.ts:2001), and the PUT route maps UnknownKeyError to 400 UNKNOWN_KEY with details.namespace and details.key (settings-routes.ts:343-346); get and getMany refuse the same way (:1386, :1499), and getNamespace passes only registered keys, so a stored theme_mode or support_email row is neither offered nor resolved. Pinned with seeded rows in settings-routes.test.ts:849-890. The 「甲」 confirmation the ruling asked for (6293406345) is met: no 18.x warning fallback is needed.
  2. The branding namespace withheld under a wall. postureEnforcesWall is posture !== 'single' (tenancy-posture.ts:53-55), so group and isolated both withhold (settings-service-plugin.ts:595-601); the manifest is then absent from listManifests (settings-service.ts:1320-1328), GET /api/settings does not list it, and GET or PUT /api/settings/branding take the unknown-namespace arm, 404 UNKNOWN_NAMESPACE (settings-routes.ts:286-287, :341-342; on PUT secretKeysOf throws it first, :327). Never a silent 200.
  3. The posture read once, at kernel:ready, through the plugin's own resolver and the shared classification (settings-service-plugin.ts:538-556, :580-593): no tenancy service or a host with no async registry answers undefined and offers (admission-tenancy-posture.test.ts:230-233 is the shared helper's own pin of that), a throw withholds with one warn naming the namespace (:586-593). Fail-closed is the direction the amendment asked for.
  4. Readers see the namespace in time. kernel:ready handlers run one after another in registration order (hook-dispatch.ts:154-156, kernel.ts:305-317); registration order is start() order; AuthPlugin declares com.objectstack.service.settings in optionalDependencies (auth-plugin.ts:453, pinned by auth-settings-ordering.pin.test.ts), so the settings hook, which offers first (settings-service-plugin.ts:258-261), runs before plugin-auth's binding (auth-plugin.ts:956, :1123-1145). The runtime-config door reads per request (runtime-config-plugin.ts:1161-1200), so no order reaches it.
  5. Runtime-config under single. The door reads only when the service lists the namespace, asks only the declared keys, with an empty context; source: 'default' is never served; a failed read serves the deployment branding and warns once per run of failures (runtime-config-plugin.ts:1163-1198). The empty context resolves unwalled under single (settings-service.ts:1672-1675, :1689-1693), an organization's row ahead of an organization-less one (:1668). The served block keeps its eight keys (:1125-1144); RuntimeConfigPluginConfig gained no member; the renamed members are private. The branding test's stand-in answers getMany even when unlisted, so the walled pin really is a pin of the listing check.
  6. No new export. service-settings/src/index.ts is byte-identical between main and the head; UNWALLED_ONLY_NAMESPACES and the nav factory are module-private.
  7. The Setup entry travels with the namespace. The registry folds contributions lazily at getApp (registry.ts:6253-6259, :6274-6277), so a kernel:ready contribution lands; priority 110 sits between platform-objects' BASE_PRIORITY 100 (setup-nav.contributions.ts:27) and messaging's 120 (messaging-service-plugin.ts:302); the pattern is the MCP bundle's (mcp/src/plugin.ts:806-811). check-app-nav-i18n.mjs consumes loaded.manifests (:848), so the roster entry keeps the id in that gate's population. The tombstone test pins labels only (setup-nav-dead-key-tombstone.test.ts), so it stays green.
  8. The cli wire pin's 32 to 31 is a count re-pin that follows the composition (that harness composes no service-settings), with an explicit not.toContain. Right, with the note that it pins the composition, not the behaviour.
  9. The CLI one-shot composers of the plugin (one-shot-settings.ts; secret orphans, secret rewrap, data migration) walk encrypted specifiers, and branding declares none, so their reports are unchanged whether or not those boots reach kernel:ready.

Named WRONG:

  1. A pinned suite outside the file list goes red, and nothing in the PR names it. packages/qa/dogfood/test/settings-organization-isolation.dogfood.test.ts boots a real isolated posture (:24, asserted at :116-119, through bootStack(crm, { multiTenant: 'posture-only' }); the harness composes the plugin with its bundled manifests, verify/src/harness.ts:1028) and uses the shipped branding manifest as its tenant-scope fixture (:33-34): PUT /api/settings/branding expected 200 from two organization owners (:135-141, :154-155, :190-191), GET expected 200 (:69-75), and the organization-less write expected 400 SETTINGS_VALIDATION (:181-185). Under this head every one of those calls answers 404 UNKNOWN_NAMESPACE (item 2). The dogfood job is gated off pull_request and merge_group (.github/workflows/ci.yml:2037), so this head's check-runs cannot show it: the three shards read skipped, and the red lands on the first full battery after merge. The card's own readings (6290871847, 4_real_pull) listed this suite as a workspace_name fixture. The other branding-writing suites (settings-config-change-audit, settings-audit-global-rung-sibling, audit-log-parent-read-gate, the upgrade harness) boot no wall and are unaffected. Remedy: re-point that suite's tenant-scope fixture to a namespace offered under a wall (company is the posture pin's own control), in this PR, declared cross-lane.
  2. An env override the diff stops honouring on walled deployments, and a changeset that says the opposite. get() serves an in-force OS_BRANDING_* override as source: 'env' before any organization reach (settings-service.ts:1406-1418), so before this diff a walled deployment that pinned OS_BRANDING_WORKSPACE_NAME had plugin-auth apply it (auth-plugin.ts:1102-1108, any source but default); so did an organization-less branding.workspace_name row from before the wall stood (:1665-1667). After this diff plugin-auth never binds under a wall (auth-plugin.ts:1123-1145), so on group and isolated that variable and that row stop naming sign-in mail, the authenticator issuer and MCP sign-in, and the OS_APP_NAME chain takes over. The ruling permits the narrowing (the namespace is not offered there); the changesets must state it. .changeset/24766-auth-brand-binding.md:11 says "That is the name such a deployment always got", which is false for either operator, and .changeset/24766-branding-settings-v2.md:25 names only OS_BRANDING_THEME_MODE and OS_BRANDING_SUPPORT_EMAIL as no longer read. AGENTS.md:1089-1091 asks the FROM and TO of a config field a breaking change stops honouring: on a walled deployment, from OS_BRANDING_WORKSPACE_NAME to OS_APP_NAME.

② Semver level

  • PR body: Clause-②: no (narrowing). @objectstack/service-settings: minor, BREAKING banner, Clause-②: no (narrowing), one ADR-0087 marker (not-required). That is AGENTS.md:1087-1088 (a narrowing is BREAKING) at the level the ruling fixed (6293406345, 6293793948: minor, 18.x under 甲). Check Changeset and the Lint families, which carry the registration gate, are success on the head. Right.
  • @objectstack/cloud-connection: minor, Clause-②: no. The served branding block and the door's accept set are unchanged; only the source of three values moves. The ruling's "widening" is the capability sense, which feat at minor carries. Right.
  • @objectstack/platform-objects: minor, Clause-②: no. The published SETUP_NAV_CONTRIBUTIONS loses a member, which a consumer composing it alone observes (the cli pin's count moved at 30fb515b1). Acceptable only because the PR-level arm is already (narrowing) with the BREAKING disposition; read on its own the arm would be (narrowing). Named, not failed.
  • @objectstack/plugin-auth: minor, Clause-②: no. The level is right; the body is wrong on item 11. Failed there.
  • Clause-②: no (narrowing) on the body and on the governing changeset, consistent. The narrowed accept sets are three, not two as the body counts: branding.theme_mode and branding.support_email on every posture; the branding namespace under a wall; and, under a wall, the settings-layer OS_BRANDING_WORKSPACE_NAME override and any organization-less workspace_name row (item 11).

③ Boundary flags

  • 6298484657 open_questions 1 to 3: answered by the seat (6298539840: A, A, A) and delivered on this head: the nav contribution (settings-service-plugin.ts:604-633), the auth gate (auth-plugin.ts:1123-1145), PR line 1 Fixes #24766. Closed.
  • 6300261952 open_questions: none. Its deviations: the cli pin edit (authorized, declared), the Setup entry's position after Feature Flags (the ruling fixed presence, not position), a throwing listManifests caught with a warn, minor on every changeset (see ②), the gate battery at 30fb515b1 re-derived at the head. Each read; closed.
  • The dev's notes with no carrier: the QA checklist step text; the anonymous-door classification wording (true by the ruling's definition under single, and the door serves nothing tenant-owned under a wall, anonymous-door-sweep.classification.ts:285-290); CONDITIONAL_SETUP_NAV_IDS (not needed: the i18n gate's new roster entry boots the id under the no-wall reading, so a boot does judge its label); the object-entry-views roster (it judges only its own roster, platform-app-object-entry-views.test.ts:316-319, no sync assertion); env-registry hosts; the two defaults (docs(deployment): the environment-variables page gives OS_PRODUCT_NAME a default the runtime does not use and omits six branding variables the runtime reads #24887). None moves the verdict. Closed.
  • Escalated by this record: item 10 (a pinned dogfood suite goes red on the first post-merge full battery) and item 11 (the walled-deployment claim in the changesets). Both are the FAIL.
  • Check-runs on the head at read time, the gate verdicts: Build Core, TypeScript Type Check, every Lint and Type Check family, Check Changeset, Check PR Size, Governed Surface Queue Guard, the three claim guards, Flag docs: success. Test Core 5/6 success; Test Core 1/6, 2/6, 3/6, 4/6 and 6/6 in_progress (the honest value; not waited on). Dogfood Regression Gate success is the aggregator: its three shards, Dogfood Verify CLI, Console Pin Gate, Temporal Conformance and Build Docs are skipped on a pull_request event, which is exactly why item 10 is invisible here.

What a PASS needs: the isolation dogfood suite re-pointed off branding (item 10), and the two changeset bodies stating the walled-deployment FROM and TO for OS_BRANDING_WORKSPACE_NAME (item 11). Nothing in the mechanism itself was found wrong.

Implemented-by: claude/issue-24766-branding-runtime-config
Reviewed-by: session_01UmqzD97e14iws6hmVhgZ5y

VERDICT: FAIL

…ompany.legal_name, offered under a wall

Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y
Co-authored-by: Claude <noreply@anthropic.com>
…WORKSPACE_NAME; Setup docs name service-settings for Branding

Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y
Co-authored-by: Claude <noreply@anthropic.com>
…nvironment setting; the ADR-0087 reasoning says why the ledger carries nothing

Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d616d4d873375890e054174bafb82ff0dc53aa8d
Local-runs: none
Scope: packages/cli/scripts/check-app-nav-i18n.mjs, packages/cli/test/connect-agent-both-halves-wire.pin.test.ts, packages/cloud-connection/src/runtime-config-branding-settings.test.ts, packages/cloud-connection/src/runtime-config-plugin.ts, packages/platform-objects/src/apps/setup-nav.contributions.ts, packages/plugins/plugin-auth/src/auth-plugin.test.ts, packages/plugins/plugin-auth/src/auth-plugin.ts, packages/qa/dogfood/test/settings-organization-isolation.dogfood.test.ts, packages/services/service-settings/README.md, packages/services/service-settings/src/manifests/branding.manifest.test.ts, packages/services/service-settings/src/manifests/branding.manifest.ts, packages/services/service-settings/src/settings-branding-posture.pin.test.ts, packages/services/service-settings/src/settings-routes.test.ts, packages/services/service-settings/src/settings-service-plugin.ts, packages/services/service-settings/src/settings-service.test.ts, packages/services/service-settings/src/translations/en.ts, packages/services/service-settings/src/translations/es-ES.ts, packages/services/service-settings/src/translations/ja-JP.ts, packages/services/service-settings/src/translations/zh-CN.ts + content/docs/ui/setup-app.mdx, packages/services/service-settings/src/manifests/company.manifest.ts, packages/services/service-settings/src/index.ts, packages/services/service-settings/src/settings-service.ts, packages/services/service-settings/src/settings-routes.ts, packages/services/service-settings/src/settings-service.types.ts, packages/spec/src/security/tenancy-posture.ts, packages/spec/src/system/settings-manifest.zod.ts, packages/core/src/security/admission-tenancy-posture.test.ts, packages/core/src/hook-dispatch.ts, packages/core/src/kernel.ts, packages/plugins/plugin-auth/src/auth-manager.ts, packages/plugins/plugin-auth/src/auth-settings-ordering.pin.test.ts, packages/objectql/src/registry.ts, packages/objectql/src/plugin.ts, packages/mcp/src/plugin.ts, packages/mcp/src/connect-ui.ts, packages/services/service-messaging/src/messaging-service-plugin.ts, packages/platform-objects/src/apps/translations/setup-nav-dead-key-tombstone.test.ts, packages/qa/dogfood/test/audit-log-parent-read-gate.dogfood.test.ts, packages/qa/dogfood/test/anonymous-door-sweep.classification.ts, packages/qa/dogfood/test/platform-app-object-entry-views.test.ts, packages/verify/src/harness.ts, packages/verify/src/harness.posture-only.test.ts, packages/cli/src/utils/one-shot-settings.ts, scripts/check-settings-bind-window.mjs, scripts/check-adr-0087-registration.mjs, scripts/adr-anchors/scripts__check-adr-0087-registration.mjs.json, .github/workflows/ci.yml, AGENTS.md

A fresh record on the new head, over the full floor. Read-only: the card (body and twelve comments), the PR body as it now stands, its file list, the net diff against main, the head's check-runs, and the cited code at the head through the object store. Nothing built, run or re-run. Measured first: git diff --stat between e6522a957 and d616d4d87 names only the five round-2 files (three changesets, the docs row, the dogfood suite) and the merge's other-PR files; every mechanism path of the PR and every file the earlier record (6300660206) cited is byte-identical between the two heads, so those readings are re-stated here against the same lines, which were re-checked at this head.

① Derived judgments

Named RIGHT, each against the committed tree at d616d4d87:

  1. Retired keys refused loudly, by name, whole batch. setMany pre-flights every key of the patch before any write (settings-service.ts:2001); the PUT route maps UnknownKeyError to 400 UNKNOWN_KEY with details.namespace and details.key (settings-routes.ts:343-346); get and getMany refuse the same way (:1386, :1499), and getNamespace passes only registered keys, so a stored theme_mode or support_email row is neither offered nor resolved. Pinned with seeded rows in settings-routes.test.ts:849-890. The 「甲」 confirmation the ruling asked for (6293406345) is met.
  2. The branding namespace withheld under a wall. postureEnforcesWall is posture !== 'single' (tenancy-posture.ts:53-55), so group and isolated both withhold (settings-service-plugin.ts:595-601); the manifest is then absent from listManifests (settings-service.ts:1320-1328), GET /api/settings does not list it, and GET or PUT /api/settings/branding take the unknown-namespace arm, 404 UNKNOWN_NAMESPACE (settings-routes.ts:286-287, :341-342; on PUT secretKeysOf throws it first, :327). Never a silent 200.
  3. The posture read once, at kernel:ready, through the plugin's own resolver and the shared classification (settings-service-plugin.ts:538-556, :580-593): no tenancy service or a host with no async registry answers undefined and offers (admission-tenancy-posture.test.ts:230-233), a throw withholds with one warn naming the namespace (:586-593). Fail-closed is the amendment's direction.
  4. Readers see the namespace in time. kernel:ready handlers run one after another in registration order (hook-dispatch.ts:154-156, kernel.ts:305-317); registration order is start() order; AuthPlugin declares com.objectstack.service.settings in optionalDependencies (auth-plugin.ts:453, pinned by auth-settings-ordering.pin.test.ts), so the settings hook, which offers first (settings-service-plugin.ts:258-261), runs before plugin-auth's binding (auth-plugin.ts:956, :1123-1145). The runtime-config door reads per request (runtime-config-plugin.ts:1161-1200), so no order reaches it.
  5. Runtime-config under single. The door reads only when the service lists the namespace, asks only the declared keys, with an empty context; source: 'default' is never served; a failed read serves the deployment branding and warns once per run of failures (runtime-config-plugin.ts:1163-1198). The empty context resolves unwalled under single (settings-service.ts:1672-1675, :1689-1693), an organization's row ahead of an organization-less one (:1668). The served block keeps its eight keys (:1125-1144); RuntimeConfigPluginConfig gained no member; the renamed members are private.
  6. No new export: service-settings/src/index.ts is byte-identical to main; UNWALLED_ONLY_NAMESPACES and the nav factory are module-private.
  7. The Setup entry travels with the namespace. The registry folds contributions lazily at getApp (registry.ts:6253-6259, :6274-6277); priority 110 sits between platform-objects' BASE_PRIORITY 100 (setup-nav.contributions.ts:27) and messaging's 120 (messaging-service-plugin.ts:302); the pattern is the MCP bundle's (mcp/src/plugin.ts:806-811). check-app-nav-i18n.mjs consumes loaded.manifests (:848). The tombstone test pins labels only.
  8. The cli wire pin's 32 to 31 is a count re-pin that follows the composition, with an explicit not.toContain; it pins the composition, not the behaviour.
  9. The CLI one-shot composers of the plugin walk encrypted specifiers, and branding declares none, so their reports are unchanged whether or not those boots reach kernel:ready.
  10. Earlier item 10, the isolation dogfood suite, CLOSED on the committed tree. settings-organization-isolation.dogfood.test.ts now uses company.legal_name as its tenant-scope fixture (:33-41, :138-169, :182-197, :221), with every assertion keeping its meaning: two owners' isolated writes and reads, the reset leaving the other untouched, the organization-less write refused 400 SETTINGS_VALIDATION with fields[0] naming legal_name, and the data-API positive control. The fixture holds by reading: company is tenant scope with setup.access / setup.write (company.manifest.ts:18-25), legal_name declares no default (:32-35), the namespace is not a member of UNWALLED_ONLY_NAMESPACES (settings-service-plugin.ts:80) and is the posture pin's own control under group and isolated (settings-branding-posture.pin.test.ts), and the organization-less refusal is collected per key, whatever the key (settings-service.ts:1989-2001). The three other branding-writing suites boot no wall (audit-log-parent-read-gate.dogfood.test.ts:70-79 boots orgContext, which the harness refuses to compose with a wall) and keep branding, rightly. The green run the dev reports is a local run, not a gate verdict: the dogfood shards stay skipped on a pull_request event (.github/workflows/ci.yml:2037), so this re-point is judged by reading, as above.
  11. Earlier item 11, the walled-deployment claim, CLOSED. .changeset/24766-auth-brand-binding.md:13-18 now states that on group / isolated the auth product name comes from the OS_APP_NAME chain alone, names the two sources that stop applying (OS_BRANDING_WORKSPACE_NAME, served as an env value whatever the posture, and an organization-less branding.workspace_name row) and gives the move to OS_APP_NAME; .changeset/24766-branding-settings-v2.md:29 and :35 state the same, and :25 now says the two retired keys' variables are read on no deployment. Each sentence checked against the code: get() serves an in-force OS_BRANDING_* override before any organization reach (settings-service.ts:1406-1418), plugin-auth applied any source but default (auth-plugin.ts:1102-1108) and now binds only where the namespace is listed (:1123-1145), and getAppName() is what every auth email, the authenticator entry and the MCP resource read (auth-manager.ts:1792, :2085, :2100, :2163), falling back to the configured appName the OS_APP_NAME chain supplies (:6490). The PR body's "Clause-② measured" now counts three narrowed accept sets, as the earlier record did.
  12. The docs row, RIGHT. content/docs/ui/setup-app.mdx:55 names service-settings for Branding, offered only where the branding namespace is (the single posture), and keeps the other Configuration entries with platform-objects; at main the row listed Branding under platform-objects, which this PR makes false. The page's other mentions (:31, :143) describe SETUP_NAV_CONTRIBUTIONS as the platform-objects-owned entries and stay true. No page under content/docs or docs/qa names theme_mode, support_email or an OS_BRANDING_* variable, so nothing else goes false. Build Docs, Check Documentation Links and Flag docs affected by code changes are success on the head.
  13. Round 2 introduced no mechanism change and no new accept-set change: the five changed files are prose and one fixture. One observation, no verdict effect, carrier none: company.manifest.ts:33 describes legal_name as possibly differing from "the organization name set under Branding", a page a walled deployment no longer offers; a pre-existing sentence this PR leaves slightly stale there.

Named WRONG: none on this head.

② Semver level

  • PR body: Clause-②: no (narrowing). @objectstack/service-settings: minor, BREAKING banner, Clause-②: no (narrowing) (24766-branding-settings-v2.md:7), one ADR-0087 marker, not-required (no-migration-prescription) (:9). That is AGENTS.md:1087-1088 (a narrowing is BREAKING) at the level the ruling fixed (6293406345, 6293793948: minor, 18.x under 甲). Right.
  • The marker's reasoning, re-read as the brief asks: the retired keys and the withheld namespace move no metadata and no sys_setting row; the one operator-facing consequence (OS_BRANDING_WORKSPACE_NAME giving way to OS_APP_NAME on a walled deployment) is a deployment environment setting that rewrites no consumer code and no metadata, so the ledger has nothing to carry. That reading holds: ADR-0087's prescription is what objectstack migrate meta reaches, and an environment variable is outside it. The gate's own rule (check-adr-0087-registration.mjs:166-178, R2) refuses a literal FROM-and-TO prescription under this category, which is what reddened acaf3231b; the head states the same move as prose (:29, :35) and the category's substance is unchanged, so the rewording is honest, not an evasion. Check Changeset is success on the head in the run that completed; a second Check Changeset run is in_progress at read time (two workflow runs on this head; the honest value, not waited on).
  • @objectstack/cloud-connection: minor, Clause-②: no. The served branding block and the door's accept set are unchanged; the ruling's "widening" is the capability sense, which feat at minor carries. Right.
  • @objectstack/platform-objects: minor, Clause-②: no (24766-setup-branding-entry.md:7; the tense fix at :9 is right). The published SETUP_NAV_CONTRIBUTIONS loses a member, which a consumer composing it alone observes. Acceptable only because the PR-level arm is already (narrowing) with the BREAKING disposition; read on its own the arm would be (narrowing). Named, not failed, as before.
  • @objectstack/plugin-auth: minor, Clause-②: no (24766-auth-brand-binding.md:7). The reasoning is now right: this package's own diff removes a read and a warn; with the package unchanged, the withheld namespace would have produced the same product name on a walled deployment, so the narrowing is service-settings', declared BREAKING there. The body's walled paragraph and its FROM-and-TO line (:13-18) are true to the code (item 11). Right.
  • Clause-②: no (narrowing) on the body and on the governing changeset, consistent; the three narrowed accept sets are now stated in both.

③ Boundary flags

  • Seat REWORK 6300683585, three items: (1) the dogfood fixture, closed by item 10; (2) the changeset FROM and TO, closed by item 11; (3) the docs row, closed by item 12.
  • Dev report 6301504200: open_questions none; out_of_scope_findings none. Its deviations, each read: the FROM-and-TO reworded as an environment fix under the gate's R2 rule (judged right in ②); two further corrected sentences ("nothing on a single-organization deployment", "would open"), both true to the code; the moved "offered again under single" sentence, same meaning; origin/main moved after the merge at 6440cf163, the overlap other PRs' changesets only (the inter-head diff confirms no mechanism path moved); the worktree removed. Closed.
  • The PR body edits the report asked for are on the body as read: the "Walled deployments, the auth product name" paragraph, the three-set Clause-② count, the Cross-lane line (packages/qa/dogfood and content/docs/ui/setup-app.mdx, declared), the Patch round 2 tests and gates. content/docs/releases/ is untouched (the file list has no such path).
  • The earlier dev notes with no carrier (the QA checklist step text, the anonymous-door classification wording, CONDITIONAL_SETUP_NAV_IDS, the object-entry-views roster, env-registry hosts, the two defaults for docs(deployment): the environment-variables page gives OS_PRODUCT_NAME a default the runtime does not use and omits six branding variables the runtime reads #24887): each re-read, none moves the verdict. Closed.
  • Check-runs on the head at read time, the gate verdicts: Build Core, Build Docs, Test Core and all six shards, TypeScript Type Check, every Lint and Type Check family, Spec property liveness, Check PR Size, Governed Surface Queue Guard, the three claim guards, Check Documentation Links, Flag docs: success. Check Changeset: success in one run, in_progress in a second. Dogfood Regression Gate success is the aggregator; its three shards, Dogfood Verify CLI, Console Pin Gate and Temporal Conformance are skipped on a pull_request event, so item 10 rests on the reading above and on the full battery after merge.

Implemented-by: claude/issue-24766-branding-runtime-config
Reviewed-by: session_01UmqzD97e14iws6hmVhgZ5y

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · #24766 · domain:services seat 2 · os-support-ai · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T21:16Z · head d616d4d873.

  • Rounds: this follows patch rounds 1 (REWORK 6298539840) and 2 (REWORK 6300683585). It is the review of record for claim 6296927167.
  • Contract review of record: 6301655792 (CONTRACT_REVIEW_TIER, narrowing face, PASS, on this head). The earlier record 6300660206 (FAIL on e6522a957) is closed by round 2.
  1. Form:
    • The first line is Fixes #24766. The seat's grep of the body finds no other closing keyword beside an issue number.
    • Clause-②: no (narrowing) is at line start in the body and in the service-settings changeset (minor, BREAKING, ADR-0087 not-required (no-migration-prescription)).
    • The cloud-connection, platform-objects and plugin-auth changesets are minor, Clause-②: no, each with its reason.
    • @objectstack/cli ships dist only (files[]), so its script and test edits owe no changeset.
    • 24 files, +1373/−174.
    • Path face, read with the files endpoint on this head: check-governed-merges --pr 24959 reports 0 of 24 paths governed. Ordinary queue landing.
  2. Premise and fix: ruling B′ (6293793948) with its amendment (6293825075), under 「甲」 ([PM seat] triage (objectstack-wide) — 🟢 Routine · session_01AavokzJ5DndAwitDXvKy4U · trig_01NcnCtMS2tH46nUg1a5TaMJ (hourly) · state = body + the round records newer than it #6015 6292005854). B′ fixes the retirement as 18.x, because the manifest step carries it.
    • Under single: GET /api/v1/runtime/config reads branding.workspace_name / logo_url / accent_color per request into productName / logoUrl / brandColor, with the OS_* variables as the fallback. source: 'default' is never served.
    • The retired keys: theme_mode and support_email retire (manifest v1 → v2). A save naming either is refused 400 UNKNOWN_KEY, and the whole batch with it.
    • Under group / isolated:
      • The branding namespace is withheld at kernel:ready: unlisted, and GET / PUT answer 404 UNKNOWN_NAMESPACE.
      • The Setup entry is contributed only with the namespace.
      • plugin-auth binds the product name only where the namespace is listed.
      • The OS_BRANDING_WORKSPACE_NAME → OS_APP_NAME move is stated in two changesets.
    • The refusal pins assert both halves of the envelope: state.status 404 / 400 and state.body.error.code UNKNOWN_NAMESPACE / UNKNOWN_KEY. Seat-read in settings-branding-posture.pin.test.ts and settings-routes.test.ts.
    • Ablations: legs 1–5 (round 0) and A, B1, B2 and C (round 1) each went red as predicted and were restored to the HEAD blob.
    • Round 2: the isolation dogfood suite was re-pointed to company.legal_name and passes 6 of 6 under isolated. Its e6522a957 version on the same code goes 4 red with UNKNOWN_NAMESPACE.
  3. Gates:
    • CI on d616d4d873, latest run per check name: 42 checks, 34 success, 8 skipped, 0 failed.
    • Every skip is on the roster in scripts/pm/check-expected-skips.mjs (read from the file): Auto Label, Check PR Size, the Dogfood Regression Gate matrix, Dogfood Verify CLI, Console Pin Gate, Temporal Conformance, the 18.x upgrade harness and Packed-tarball smoke.
    • Lint & Repo Gates and TypeScript Type Check are both success, and both Check Changeset runs on this head are success.
    • Earlier reds were each closed on a later head:
      • the plugin-auth test-typecheck TS2345 (edce05245);
      • the cli wire pin's stale Setup count (30fb515b1);
      • an ADR-0087 marker that a literal FROM/TO label broke (acaf3231b).
    • Local: 106 derived, 106 run, 0 NOT-MEASURED at this head.
  4. Accepted deviations:
    • The Setup → Branding entry now sits after Feature Flags (contribution priority 110): the ruling fixed presence, not position.
    • The cli wire pin re-counts to 31 with an explicit not.toContain('nav_settings_branding'): it pins that harness's composition.
    • A throwing listManifests is caught with one warn in plugin-auth.
    • minor on every changeset.
    • The FROM/TO is worded as the environment fix it is in the BREAKING changeset, under the registration gate's rule; the contract review judged the rewording honest.
    • Cross-lane files, declared:
      • cloud-connection (by B′);
      • platform-objects;
      • the cli script and test;
      • packages/qa/dogfood (domain:cli);
      • content/docs/ui/setup-app.mdx (domain:devx).
    • The dev handed the PR-body edits to the seat, and the seat applied them.
    • Prose read sentence by sentence against the diff and the cited code:
      • in the four changesets, the three keys' sources and fallbacks, the retired keys' refusal and kept rows, the walled withholding and its Setup entry, the walled OS_BRANDING_* paragraph with the OS_APP_NAME move, and the "What to do" bullets;
      • the setup-app.mdx Configuration row.
  5. Out of scope:
  6. Landing: ordinary queue landing. Every check on this head is green or an expected skip, and the contract review of record is a PASS on this head, so the seat flips it (pr_ready + automerge_enable) on this record. After the merge:

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 20, 2026 21:18
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 20, 2026 21:18
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 20, 2026
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

Development

Successfully merging this pull request may close these issues.

Setup → Branding saves, but the workspace name, accent colour and logo change nothing in the app

2 participants