You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
console: single-membership users get no current-organization indicator on multi-org (isolated) deployments — workspace-switcher only renders for multi-membership users #5287
On a multi-org deployment (tenancy posture: isolated, enterprise @objectstack/organizations mounted), a business user who is a member of exactly one organization gets no indication anywhere in the console of which organization they are in: the top bar shows no current-organization name, and the workspace-switcher control does not render at all for them. Only users with multiple memberships (e.g. an admin joined to two orgs) get the switcher — which doubles as the only place the current org name is displayed.
With row-level isolation active, "which organization am I looking at" is load-bearing context (all lists are silently scoped). A single-membership user has no way to tell.
Repro
Boot a host app with OS_MULTI_ORG_ENABLED=true and a licensed @objectstack/organizations (posture isolated, not degraded); two organizations exist.
Create user A as a member of org 1 only (membership policy invite-only, default active org = org 1). Create admin as member of both orgs.
Log in as admin: top bar renders the workspace switcher with both orgs, current org name visible. ✔
Log in as user A: no org name anywhere on the page (checked full-page text), and the workspace-switcher element is absent — probed at 3s/8s/15s/22s after load to rule out delayed hydration.
Observed on @objectstack/*@17.0.0 (console served by the host app), Chrome.
Ask
Render a read-only "current organization" indicator for single-membership users (switcher itself can stay hidden when there is nothing to switch to). App projects have no metadata hook to provide this themselves — the top bar is console-owned.
Found during a downstream project's multi-tenant acceptance run (steedos-labs/os-project-titanwind-ehr #1577, case S20-001; screenshots archived there).
Triage (first-touch, triage seat, session session_01HXzdkKx5WjwwCTT3c7U5kB): filed correctly in objectui (console-owned top bar); type Feature — rendering a current-organization indicator adds UI surface, so this is a maintainer call, not a dispatchable defect. needs-user-decision. Real measured pull attached (downstream acceptance run, case S20-001 with archived screenshots).
<!-- os-decision-facets -->
platform long-term coherence: under isolated tenancy "which org am I in" is load-bearing context, and the console owns the top bar — a read-only indicator for single-membership users completes the multi-org surface consistently (switcher stays the multi-membership affordance) rather than adding a special case; app projects have no metadata hook to self-serve this.
measured business pull: measured, not speculative — a downstream project's multi-tenant acceptance run failed on it (steedos-labs/os-project-titanwind-ehr#1577 case S20-001); any single-membership business user on an isolated deployment is affected today.
AI-agent error-resistance: neutral-to-positive — a console-owned indicator prevents each downstream app from hacking its own org display (the workaround pressure is already visible in the filing).
startup scope discipline: small bounded addition (render an already-session-known datum read-only); still a permanent console surface, and Feature-class stays a human call.
Recommendation: A — when the org context is active and the user has exactly one membership, render the current organization name read-only in the top bar; switcher continues to render only for multi-membership. Options: A (read-only indicator) / B (always render switcher, single-entry disabled) / C (decline; document that single-membership users see no org name).
Confidence gap: not verified whether the top bar already receives the active-org datum for single-membership sessions (may need a fetch, which changes the cost), nor whether other tenancy postures expect the bar to stay org-silent.
Claim: PM loop round 17
Session: session_01RV6yuVCxymHYE16PL9vQkE
Branch: claude/issue-5287-single-membership-org-indicator
Worktree: objectui-issue-5287
Domain: repo:objectui (execution seat)
File surface: the console top bar and the workspace-switcher region — expected apps/console/src/** and/or the packages/app-shell top-bar component that renders it, declare the exact files in your report once you have located them (the card names the surface by behaviour, not by path, so the first honest deliverable is the path). ⛔ OUT: packages/cli/** (#5127, in flight), content/docs/guide/** + scripts/check-doc-snippet-types.mjs (#5160, in flight), packages/plugin-grid/src/ObjectGrid.tsx (#5148, in flight). apps/console/vite.config.ts is now free — PR5323 merged this round; rebase on current main.
Container & model: M, mode:subagent, model: opus — the render is small; the precondition below is where the judgement is.
Clause-②: no — this renders a datum the session already carries (or fetches one, see the precondition). No schema accept/reject boundary moves and no authoring surface widens. The card's type is Feature, which is why it went to the maintainer first; it has been ruled, so it is now ordinary dispatchable work.
Serial constraints cleared: nothing open touches the console top bar. PR5327 (#5309, app-shell/.../metadata-admin/inspectors/**) merged this round — if your surface lands in packages/app-shell, it is a different subtree and the file is free; still rebase and re-read.
Zone 1 — RULED. Option A. Do not re-open the ballot.
Maintainer, 2026-08-19, verbatim 「全部接受」, recorded on this card:
Ruling: Option A — read-only current-organization indicator for single-membership users
The console top bar renders the current organization name read-only when org context is active and the user has exactly one membership; the workspace switcher itself stays hidden when there is nothing to switch to. Option B (switcher with one disabled entry) and C (decline + document) are rejected.
Implementation precondition from the confidence gap: first verify whether the top bar already receives the active-org datum for single-membership sessions — if a new fetch is needed, size it in the PR rather than assuming.
⛔ Do not implement B by accident. "Render the switcher but disable it" is the rejected option, and it is the shape you will drift into if you start from the existing switcher component and relax its render condition. The switcher's visibility rule is unchanged; what you are adding is a separate read-only indicator for the case the switcher declines to render.
Zone 2 — the precondition is a deliverable, not a preamble
Measure, before you write the component, whether the top bar already has the active organization's name for a single-membership session. Three outcomes, and they have very different costs:
Already present (the session/context already carries it) — render it; this is a small PR.
Present but only populated on the multi-membership path — the datum exists and something skips fetching or storing it for single-membership users. Say exactly where, then fix that rather than adding a second source.
Genuinely absent — a new fetch is needed. Size it in the PR body — what call, on what render, cached how. ⛔ Do not add an unconditional per-page-load request without saying so plainly.
Report which of the three you measured, with the file and line. A zero is not a reading until you counter-probe it with a datum you know the bar does receive.
The other half of the confidence gap — check it, do not assume it
The triage note also flags: "nor whether other tenancy postures expect the bar to stay org-silent." So: what does a single-org / non-isolated / degraded deployment render after your change? The ruling scopes the indicator to "when org context is active" — find what makes that predicate true in this codebase and gate on it. A single-tenant deployment growing an organization name in its top bar would be a visible regression for every existing user, and it is the most likely way this change goes wrong.
Tests
Org context active + exactly one membership ⇒ the organization name is rendered, read-only, and no switcher control is present.
Org context active + multiple memberships ⇒ unchanged: switcher renders, current org visible through it. ⛔ Do not regress this path.
Org context inactive / single-tenant posture ⇒ nothing new renders. This is the regression guard; write it even though it feels obvious.
The indicator is genuinely read-only — no click target, no menu.
Reverse-verify: restore the component, keep the tests, predict which go red before running, report predicted vs observed. State per leg whether a build artifact sits between the edit and the thing under test (if your surface is in packages/app-shell and the test runs from apps/console, one does).
Gates
Derive from your diff (scripts/pm/dispatch-gates.mjs is objectstack-only). Run vitest from the repo root — note the reason, because the guard's own header states it wrongly: it is not that a package-scoped run is refused (17 packages own a standalone vitest.config.ts and are not refused), it is that a package-scoped run uses a different config than CI does, so its green is not CI's green. Changeset: run check-changeset-presence.mjs and follow its verdict — apps/console is not a released package, packages/app-shell is. patch or minor; ⛔ never major (maintainer, 2026-08-19: 「objectui ui 的 major 是要跟着 objectstack 走的,不可以自己发 major」).
⛔ Do not background a long check and end your turn. ⛔ No exhaustive paginated API sweeps.
Deliver
Draft PR against main, first body line Fixes #5287. Push the branch early. Return the JSON report and post it as a comment whose first line is the literal <!-- os-dev-report --> marker.
One note on provenance: this card was filed by a human from a downstream project's acceptance run (case S20-001), not by a sweep. The failing scenario is real and someone is waiting on it — if you find the fix is larger than the ruling assumed, say so early rather than shipping something narrower than what was ruled.
{
"issue": 5287,
"status": "done",
"branch": "claude/issue-5287-single-membership-org-indicator",
"pr": "https://github.com/objectstack-ai/objectui/pull/5339",
"premise_still_valid": true,
"summary": "Option A as ruled. New `packages/app-shell/src/layout/CurrentOrganizationIndicator.tsx` renders the active organization name read-only (a bare span: no trigger, no menu, no focusable node) when the tenancy posture puts an organization wall in force and the user has exactly one membership; mounted in `AppHeader.tsx` beside `WorkspaceSwitcher`, whose `orgList.length <= 1` early return is untouched — the switcher stays hidden, which is the ruled shape rather than rejected option B. FILE SURFACE (the card named it by behaviour): the top bar is not in `apps/console` at all — it is `@object-ui/app-shell`. Changed: `packages/app-shell/src/layout/{CurrentOrganizationIndicator.tsx,OrgBadge.tsx}` (new), `packages/app-shell/src/layout/{AppHeader.tsx,WorkspaceSwitcher.tsx}`, `packages/app-shell/src/hooks/useTenancyPosture.ts`, three new test files, `packages/i18n/src/locales/*.ts` (ten packs, one key `organization.current.label`), one changeset. PRECONDITION = OUTCOME 1, ALREADY PRESENT, ZERO NEW REQUESTS: `packages/auth/src/AuthProvider.tsx:646-652` loads organizations for every authenticated session with no membership-count gate, and `:567-611` (`refreshOrganizations`, with ADR-0081's single-membership repair at `:583-590` calling `setActiveOrganization(orgs[0].id)` when `orgs.length === 1`) populates `activeOrganization`; `AppHeader.tsx:151-154` already destructures it and `WorkspaceSwitcher.tsx:76-77` already computes `activeOrganization ?? orgList[0]` before discarding it. Measured against the REAL AuthProvider, kept as a pin. The DOM zero was counter-probed before being believed: same render, same queries, org name absent while the user's own name was present. OTHER HALF OF THE GAP (non-isolated postures): gated on ADR-0105's `postureEnforcesWall` rule — true for `isolated`/`group`, false for `single`; absent, unrecognized, in-flight or failed config renders nothing, so single-tenant and degraded deployments grow no new chrome. JUDGEMENT CALL WORTH A LOOK: importing `postureEnforcesWall` from `@objectstack/spec/security` would have been the drift-proof spelling, but measured with the repo's esbuild it costs +237.3 KB minified on the eager top-bar chunk even where the whole spec root entry is already present (1271.8 -> 1509.1 KB; the subpath's schema modules are a separate graph and `@objectstack/spec` declares no `sideEffects`). So the rule is spelled locally as `postureHasOrgWall` in `useTenancyPosture.ts` and held to the protocol by a parity test that imports the spec's real predicate and iterates `TENANCY_POSTURES` — drift fails CI, the bytes stay out of the bundle. The measurement is filed upstream (see out_of_scope_findings).",
"tests": "All runs from the repo ROOT (a package-scoped run would use a different config than CI does, so its green is not CI's green), each wrapped in `flock -E 99 -w 540 /tmp/os-heavy-verify.lock`. At final commit b2999569a: `pnpm exec vitest run packages/app-shell/src/layout packages/app-shell/src/hooks` -> 'Test Files 54 passed (54) / Tests 432 passed (432)'; `pnpm exec vitest run packages/i18n packages/auth` -> 'Test Files 64 passed (64) / Tests 1018 passed (1018)'; the four new/changed files -> 'Test Files 4 passed (4) / Tests 22 passed (22)'. `pnpm --filter @object-ui/app-shell --filter @object-ui/auth --filter @object-ui/i18n type-check` -> each echoed 'type-check$ tsc --noEmit && tsc -p tsconfig.test.json' then 'Done' (script name echoed, so no zero-match silent pass; objectui spells it `type-check`, not `typecheck`). Dependency closure built first: `pnpm --filter '@object-ui/app-shell^...' build`. Gates at b2999569a, all exit 0: check:i18n-keys ('Every in-scope call-site key resolves against the en pack (2904 keys), every literal inline defaultValue matches the value the pack serves'), check:i18n-drift ('0 en value(s) changed (1 key(s) added)'), check:control-bytes ('scanned 4729 tracked text file(s)'), check-changeset-presence ('19 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)'), check-changeset-no-major, check:phantom-deps, check:self-import; eslint on the 9 changed files -> 0 errors, 39 warnings all of the `any`/unused-destructure class the sibling AppHeader test harnesses already carry (that file: 15 of the same). Changeset: minor for @object-ui/app-shell and @object-ui/i18n; never major. REVERSE VERIFICATION, two legs, predicted before running. Build artifact between edit and subject: NONE — every `@object-ui/*` specifier is aliased to `src` in `vitest.config.mts:245-283` (verified, not assumed) and the tests import the components by relative path; the only dist involved is `@objectstack/spec` in the parity test, an unmodified published dependency, so neither leg needed a rebuild and none was performed. Leg 1, ablate the render (indicator returns null = today's behaviour): predicted 5 red — 4 in CurrentOrganizationIndicator.test.tsx, 1 in AppHeader.singleMembershipOrg.test.tsx, 0 in the parity file, 0 in the auth measurement. Observed exactly that: 'Tests 5 failed | 17 passed (22)'. The auth measurement staying green is the point: it measures the datum, not the render. Leg 2, ablate the gate (`postureHasOrgWall` always true): predicted 8 red — 4 component negatives, 2 header negatives, 2 parity. Observed 9 failed | 13 passed — ONE MORE than predicted; the miss is mine, not the code's: `postureHasOrgWall(undefined)` also flips true under that ablation, so the parity file's 'unresolved posture reads as no wall' case went red too. Both restores verified byte-clean (0 ablation markers, `git status` clean apart from the then-untracked changeset) and the suite re-run green.",
"open_questions": [],
"out_of_scope_findings": [
"filed as objectstack-ai/objectstack#10031 (unassigned, no label): `@objectstack/spec` declares no `sideEffects`, so importing one pure function from a subpath materializes that entry's whole module graph — measured +237.3 KB minified for `/security`, identical delta whether the consumer already holds the spec root entry or only `/ui`. Pushes consumers toward re-deriving protocol rules locally, which is the drift ADR-0105 exists to prevent; this PR paid that cost with a parity test rather than the bundle."
]
}
Activity
os-support-ai commentedon Aug 19, 2026
Triage (first-touch, triage seat, session
session_01HXzdkKx5WjwwCTT3c7U5kB): filed correctly in objectui (console-owned top bar); type Feature — rendering a current-organization indicator adds UI surface, so this is a maintainer call, not a dispatchable defect.needs-user-decision. Real measured pull attached (downstream acceptance run, case S20-001 with archived screenshots).<!-- os-decision-facets -->
isolatedtenancy "which org am I in" is load-bearing context, and the console owns the top bar — a read-only indicator for single-membership users completes the multi-org surface consistently (switcher stays the multi-membership affordance) rather than adding a special case; app projects have no metadata hook to self-serve this.Recommendation: A — when the org context is active and the user has exactly one membership, render the current organization name read-only in the top bar; switcher continues to render only for multi-membership. Options: A (read-only indicator) / B (always render switcher, single-entry disabled) / C (decline; document that single-membership users see no org name).
Confidence gap: not verified whether the top bar already receives the active-org datum for single-membership sessions (may need a fetch, which changes the cost), nor whether other tenancy postures expect the bar to stay org-silent.
Generated by Claude Code
os-support-ai commentedon Aug 19, 2026
Claim: PM loop round 17
Session:
session_01RV6yuVCxymHYE16PL9vQkEBranch:
claude/issue-5287-single-membership-org-indicatorWorktree:
objectui-issue-5287Domain:
repo:objectui(execution seat)File surface: the console top bar and the workspace-switcher region — expected
apps/console/src/**and/or thepackages/app-shelltop-bar component that renders it, declare the exact files in your report once you have located them (the card names the surface by behaviour, not by path, so the first honest deliverable is the path). ⛔ OUT:packages/cli/**(#5127, in flight),content/docs/guide/**+scripts/check-doc-snippet-types.mjs(#5160, in flight),packages/plugin-grid/src/ObjectGrid.tsx(#5148, in flight).apps/console/vite.config.tsis now free — PR5323 merged this round; rebase on currentmain.Container & model:
M,mode:subagent,model: opus— the render is small; the precondition below is where the judgement is.Clause-②: no — this renders a datum the session already carries (or fetches one, see the precondition). No schema accept/reject boundary moves and no authoring surface widens. The card's type is
Feature, which is why it went to the maintainer first; it has been ruled, so it is now ordinary dispatchable work.Serial constraints cleared: nothing open touches the console top bar. PR5327 (#5309,
app-shell/.../metadata-admin/inspectors/**) merged this round — if your surface lands inpackages/app-shell, it is a different subtree and the file is free; still rebase and re-read.Zone 1 — RULED. Option A. Do not re-open the ballot.
Maintainer, 2026-08-19, verbatim 「全部接受」, recorded on this card:
⛔ Do not implement B by accident. "Render the switcher but disable it" is the rejected option, and it is the shape you will drift into if you start from the existing switcher component and relax its render condition. The switcher's visibility rule is unchanged; what you are adding is a separate read-only indicator for the case the switcher declines to render.
Zone 2 — the precondition is a deliverable, not a preamble
Measure, before you write the component, whether the top bar already has the active organization's name for a single-membership session. Three outcomes, and they have very different costs:
Report which of the three you measured, with the file and line. A zero is not a reading until you counter-probe it with a datum you know the bar does receive.
The other half of the confidence gap — check it, do not assume it
The triage note also flags: "nor whether other tenancy postures expect the bar to stay org-silent." So: what does a single-org / non-isolated / degraded deployment render after your change? The ruling scopes the indicator to "when org context is active" — find what makes that predicate true in this codebase and gate on it. A single-tenant deployment growing an organization name in its top bar would be a visible regression for every existing user, and it is the most likely way this change goes wrong.
Tests
Reverse-verify: restore the component, keep the tests, predict which go red before running, report predicted vs observed. State per leg whether a build artifact sits between the edit and the thing under test (if your surface is in
packages/app-shelland the test runs fromapps/console, one does).Gates
Derive from your diff (
scripts/pm/dispatch-gates.mjsis objectstack-only). Run vitest from the repo root — note the reason, because the guard's own header states it wrongly: it is not that a package-scoped run is refused (17 packages own a standalonevitest.config.tsand are not refused), it is that a package-scoped run uses a different config than CI does, so its green is not CI's green. Changeset: runcheck-changeset-presence.mjsand follow its verdict —apps/consoleis not a released package,packages/app-shellis.patchorminor; ⛔ nevermajor(maintainer, 2026-08-19: 「objectui ui 的 major 是要跟着 objectstack 走的,不可以自己发 major」).⛔ Do not background a long check and end your turn. ⛔ No exhaustive paginated API sweeps.
Deliver
Draft PR against
main, first body lineFixes #5287. Push the branch early. Return the JSON report and post it as a comment whose first line is the literal<!-- os-dev-report -->marker.One note on provenance: this card was filed by a human from a downstream project's acceptance run (case S20-001), not by a sweep. The failing scenario is real and someone is waiting on it — if you find the fix is larger than the ruling assumed, say so early rather than shipping something narrower than what was ruled.
Generated by Claude Code
os-support-ai commentedon Aug 19, 2026
{ "issue": 5287, "status": "done", "branch": "claude/issue-5287-single-membership-org-indicator", "pr": "https://github.com/objectstack-ai/objectui/pull/5339", "premise_still_valid": true, "summary": "Option A as ruled. New `packages/app-shell/src/layout/CurrentOrganizationIndicator.tsx` renders the active organization name read-only (a bare span: no trigger, no menu, no focusable node) when the tenancy posture puts an organization wall in force and the user has exactly one membership; mounted in `AppHeader.tsx` beside `WorkspaceSwitcher`, whose `orgList.length <= 1` early return is untouched — the switcher stays hidden, which is the ruled shape rather than rejected option B. FILE SURFACE (the card named it by behaviour): the top bar is not in `apps/console` at all — it is `@object-ui/app-shell`. Changed: `packages/app-shell/src/layout/{CurrentOrganizationIndicator.tsx,OrgBadge.tsx}` (new), `packages/app-shell/src/layout/{AppHeader.tsx,WorkspaceSwitcher.tsx}`, `packages/app-shell/src/hooks/useTenancyPosture.ts`, three new test files, `packages/i18n/src/locales/*.ts` (ten packs, one key `organization.current.label`), one changeset. PRECONDITION = OUTCOME 1, ALREADY PRESENT, ZERO NEW REQUESTS: `packages/auth/src/AuthProvider.tsx:646-652` loads organizations for every authenticated session with no membership-count gate, and `:567-611` (`refreshOrganizations`, with ADR-0081's single-membership repair at `:583-590` calling `setActiveOrganization(orgs[0].id)` when `orgs.length === 1`) populates `activeOrganization`; `AppHeader.tsx:151-154` already destructures it and `WorkspaceSwitcher.tsx:76-77` already computes `activeOrganization ?? orgList[0]` before discarding it. Measured against the REAL AuthProvider, kept as a pin. The DOM zero was counter-probed before being believed: same render, same queries, org name absent while the user's own name was present. OTHER HALF OF THE GAP (non-isolated postures): gated on ADR-0105's `postureEnforcesWall` rule — true for `isolated`/`group`, false for `single`; absent, unrecognized, in-flight or failed config renders nothing, so single-tenant and degraded deployments grow no new chrome. JUDGEMENT CALL WORTH A LOOK: importing `postureEnforcesWall` from `@objectstack/spec/security` would have been the drift-proof spelling, but measured with the repo's esbuild it costs +237.3 KB minified on the eager top-bar chunk even where the whole spec root entry is already present (1271.8 -> 1509.1 KB; the subpath's schema modules are a separate graph and `@objectstack/spec` declares no `sideEffects`). So the rule is spelled locally as `postureHasOrgWall` in `useTenancyPosture.ts` and held to the protocol by a parity test that imports the spec's real predicate and iterates `TENANCY_POSTURES` — drift fails CI, the bytes stay out of the bundle. The measurement is filed upstream (see out_of_scope_findings).", "tests": "All runs from the repo ROOT (a package-scoped run would use a different config than CI does, so its green is not CI's green), each wrapped in `flock -E 99 -w 540 /tmp/os-heavy-verify.lock`. At final commit b2999569a: `pnpm exec vitest run packages/app-shell/src/layout packages/app-shell/src/hooks` -> 'Test Files 54 passed (54) / Tests 432 passed (432)'; `pnpm exec vitest run packages/i18n packages/auth` -> 'Test Files 64 passed (64) / Tests 1018 passed (1018)'; the four new/changed files -> 'Test Files 4 passed (4) / Tests 22 passed (22)'. `pnpm --filter @object-ui/app-shell --filter @object-ui/auth --filter @object-ui/i18n type-check` -> each echoed 'type-check$ tsc --noEmit && tsc -p tsconfig.test.json' then 'Done' (script name echoed, so no zero-match silent pass; objectui spells it `type-check`, not `typecheck`). Dependency closure built first: `pnpm --filter '@object-ui/app-shell^...' build`. Gates at b2999569a, all exit 0: check:i18n-keys ('Every in-scope call-site key resolves against the en pack (2904 keys), every literal inline defaultValue matches the value the pack serves'), check:i18n-drift ('0 en value(s) changed (1 key(s) added)'), check:control-bytes ('scanned 4729 tracked text file(s)'), check-changeset-presence ('19 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)'), check-changeset-no-major, check:phantom-deps, check:self-import; eslint on the 9 changed files -> 0 errors, 39 warnings all of the `any`/unused-destructure class the sibling AppHeader test harnesses already carry (that file: 15 of the same). Changeset: minor for @object-ui/app-shell and @object-ui/i18n; never major. REVERSE VERIFICATION, two legs, predicted before running. Build artifact between edit and subject: NONE — every `@object-ui/*` specifier is aliased to `src` in `vitest.config.mts:245-283` (verified, not assumed) and the tests import the components by relative path; the only dist involved is `@objectstack/spec` in the parity test, an unmodified published dependency, so neither leg needed a rebuild and none was performed. Leg 1, ablate the render (indicator returns null = today's behaviour): predicted 5 red — 4 in CurrentOrganizationIndicator.test.tsx, 1 in AppHeader.singleMembershipOrg.test.tsx, 0 in the parity file, 0 in the auth measurement. Observed exactly that: 'Tests 5 failed | 17 passed (22)'. The auth measurement staying green is the point: it measures the datum, not the render. Leg 2, ablate the gate (`postureHasOrgWall` always true): predicted 8 red — 4 component negatives, 2 header negatives, 2 parity. Observed 9 failed | 13 passed — ONE MORE than predicted; the miss is mine, not the code's: `postureHasOrgWall(undefined)` also flips true under that ablation, so the parity file's 'unresolved posture reads as no wall' case went red too. Both restores verified byte-clean (0 ablation markers, `git status` clean apart from the then-untracked changeset) and the suite re-run green.", "open_questions": [], "out_of_scope_findings": [ "filed as objectstack-ai/objectstack#10031 (unassigned, no label): `@objectstack/spec` declares no `sideEffects`, so importing one pure function from a subpath materializes that entry's whole module graph — measured +237.3 KB minified for `/security`, identical delta whether the consumer already holds the spec root entry or only `/ui`. Pushes consumers toward re-deriving protocol rules locally, which is the drift ADR-0105 exists to prevent; this PR paid that cost with a parity test rather than the bundle." ] }Generated by Claude Code