fix(authz): carry derived posture rung on ExecutionContext (#2947) - #2956
Merged
Conversation
The ADR-0095 D2 posture ladder is derived once by the shared authz resolver from capability grants, but both entry points that build the ExecutionContext (rest-server, runtime resolveExecutionContext) dropped it — so any enforcement-side reader of context.posture always saw undefined (the same drop that forced the explain layer to re-derive it, #2949). - spec: ExecutionContextSchema gains an optional `posture` field (reuses AuthzPostureSchema; no api-surface change — schema tracked as a const). - rest + runtime: plumb authz.posture through when present. - tests: assert PLATFORM_ADMIN / MEMBER are carried and guest stays unset. Additive and behavior-preserving: no enforcement decision consumes posture yet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QRUvVfpvSycAHMMF2xTxs
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 105 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
July 15, 2026 09:56
This was referenced Jul 15, 2026
os-zhuang
added a commit
that referenced
this pull request
Jul 17, 2026
…nt path, no explicit deny (#3109) Follow-through on ADR-0095: the derived posture rung is carried on the ExecutionContext since #2956 but no enforcement decision reads it — each tier decision re-derives principal tier from capability bits at its own site, the divergence class behind #2946 Finding 2 (org admin crossed the tenant wall) and #2949 (explain/enforcement posture split). - D1: posture becomes the single tier-adjudication input (Layer 0 exemption, Layer 1 short-circuit tier, explain); capability probe demoted to narrowing fallback; behavior-preserving behind an equivalence gate in the authz matrix. - D2: normative posture → layer action map with six invariants (TENANT_ADMIN never crosses Layer 0; enforcement and explain read the same carried value; posture selects tier, never side; never bypasses system-only write guards). - D3: EXTERNAL rung activation chain (audience:'external' → derivePosture → explicit-shares-only injection), matrix cells land now, implementation demand-gated on an external principal type (portal track). - D4: explicit deny/muting rejected as an adjudication primitive; needs mapped to capability gates / FLS / admission / validity windows; assembly-time muting reserved to a future permission-set-groups ADR. Composes with ADR-0096 (admission vs tiering — orthogonal axes). Number 0099 verified free of open-PR claims before push. Claude-Session: https://claude.ai/code/session_019QRUvVfpvSycAHMMF2xTxs Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Jul 18, 2026
os-zhuang
added a commit
that referenced
this pull request
Jul 18, 2026
…ture rung (#3211 M2) (#3226) The Layer 0 cross-tenant exemption gate in computeLayeredRlsFilter now reads the carried ctx.posture rung (#2956) as authoritative: crossing the tenant wall requires PLATFORM_ADMIN. The hasPlatformAdminPosture capability probe demotes to a fallback for resolver-less contexts (delegated-admin bridge, sharing service, getReadFilter consumers), where behavior is byte-for-byte unchanged. Read and write (insert/update post-image) tenant checks share the one decision, so they cannot drift. A probe/rung disagreement logs an [authz/ADR-0099] defect breadcrumb and enforces the narrower rung verdict. Retires the stale 'posture not plumbed' comment. Security narrowing (multi-org / @objectstack/organizations only, the G1- adjudicated deltas): a scoped admin_full_access grant (a) and a piecemeal platform-capability grant (b) resolve below PLATFORM_ADMIN and are now walled to their own org on private / platform-global / better-auth objects, where the posture-blind probe used to exempt them. Fail-safe (rung is a strict subset of the probe, ADR-0099 I3); recover with an unscoped admin_full_access grant. Single-org / env-per-database unaffected. authz-matrix-gate.test.ts: new ADR-0099 P1 describe (narrowing read/write, PLATFORM_ADMIN stays exempt, TENANT_ADMIN stays walled per I1, resolver-less fallback preserved, defect-breadcrumb on disagreement / silence on agreement). dogfood multi-tenant-exemption-posture ledger note updated. Docs: authorization.mdx records the unscoped-grant rule. Verified: plugin-security 503 passed, dogfood 296 passed, build 71/71, tsc + check:role-word + check:doc-authoring clean. Refs #3211 · ADR-0099 / #3109 · #2946 Finding 2 · #2956 Claude-Session: https://claude.ai/code/session_01DAHp4K7FvyMPBY1DPNkmRu Co-authored-by: Claude <noreply@anthropic.com>
13 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
目的
关闭 #2947 —— B2/B4 posture 阶梯(ADR-0095 D2:
PLATFORM_ADMIN > TENANT_ADMIN > MEMBER > EXTERNAL)由共享 authz 解析器从 capability 授予派生(resolve-authz-context.ts:303),但两个构建ExecutionContext的入口都把它丢弃了,导致 enforcement 侧任何读context.posture的代码永远拿到undefined(也是 explain 层不得不自己重算 posture、#2949 修补的那处分叉的根因)。改动(全部加法、行为不变)
ExecutionContextSchema新增可选posture字段(复用AuthzPostureSchema,无循环依赖)。api-surface 无变化(schema 作为 const 导出,字段不计入表面)。rest-server.ts构建 ctx 时携带authz.posture(present 时)。resolveExecutionContext同样透传(guest → 不设,符合「仅认证主体有 rung」)。resolve-execution-context.test.ts断言 PLATFORM_ADMIN / MEMBER 被携带、guest 保持 unset(29 passed)。为什么安全
行为完全保持:当前没有任何 enforcement 裁决消费
posture——是否让热路径「按 posture 裁决」是更大的 ADR 级决策,本 PR 不涉及。这里只是停止丢弃已算好的值,让 enforcement 与 explain 读到同一个派生 rung,为后续把 #2946 的「平台专属能力探针」收敛成直接读ctx.posture铺路。验证
@objectstack/specbuild ✓ · api-surface--checkunchanged ✓@objectstack/runtime+@objectstack/restbuild(tsc typecheck)✓resolve-execution-context.test.ts— 29 passed ✓check:role-word✓ · ESLint(改动文件)✓changeset:
spec: minor(新增公共字段)+runtime/rest: patch。非破坏,无需allow-major。关联:ADR-0095 D2 · #2949(explain 侧分叉)· #2946(未来收敛点)· tracking #2920。
🤖 Generated with Claude Code
https://claude.ai/code/session_019QRUvVfpvSycAHMMF2xTxs
Generated by Claude Code