Skip to content

feat: resolve dependencies on the remaining conversation mint sites + e2e suite - #1944

Open
serguei-gorokhov wants to merge 2 commits into
rd/4-request-start-resolutionfrom
rd/5-mint-sites-hardening
Open

serguei-gorokhov wants to merge 2 commits into
rd/4-request-start-resolutionfrom
rd/5-mint-sites-hardening

Conversation

@serguei-gorokhov

@serguei-gorokhov serguei-gorokhov commented Sep 9, 2026

Copy link
Copy Markdown

Applicable issues

Description of changes

This story deliberately folds two halves into one PR — the mixed diff is intentional (the whole-branch review hardening commit is chronologically adjacent to the mint-site commit); reviewers should assess both.

Half 1 — mint-site coverage on the conversation APIs (ad900889). ResolveResourceDependenciesFn now runs in the same chain position on the Anthropic Messages (MessagesBaseController.prepareUpstreamRoute) and Responses (ResponsesController.handleRequestBody) entry points, completing the v1 conversation mint-site coverage (route/feature/deploy sites remain a documented v1 limitation — an app invoked through those surfaces gets no dependency grants in v1). ResourceDependencyApiTest covers the v1 checklist as test cases: the consented flow, required-without-consent failing the call explicitly, optional-without-consent degrading silently, withdrawal stopping the run immediately, declaration change invalidating the grant (content binding), public-target grants following the originating user's own reach exactly, and audit events carrying app, targets, user and trace id (the grant line also carries access types).

Half 2 — whole-branch review hardening (2a40b3f6). Three real defects and a defense-in-depth gap, all in the composed behavior:

  • Root-call-only guard semantics. The timing guard threw IllegalStateException on every hop that arrives with a per-request key — the normal state for an interceptor's final call back to a declaring app (breaking even the root user request) and for every chained call into one (400/500 on all three interfaces). Resolution is now honestly root-call-only: hops under a per-request key are skipped — callable, just without grants — because the originating user's reach is not evaluable under a key context (buildUserBucket flips to the parent app's bucket). Chained composition is recorded as phase-2 machinery.
  • Internal-type ban. The placeholder type segment now enforces the validator's PERSONAL_TYPED_ROOTS vocabulary (constant made public, shared): ResourceTypes.of() maps internal engine types, so a current-user/credentials/ declaration from a config-file or admin-authored app could otherwise resolve into the user's secret-bearing blobs.
  • Union grants. Two records sharing a target combine rights instead of overwriting each other; malformed concrete paths raise RuntimeException shapes that are now caught (unresolvable, never a crash); target permission lookups are batched into one rule-chain walk; consent is checked before any resolution work; the audit's access-type join tolerates null elements. The perRequestReceivers[app] half of the bake — which never propagated on plain chained calls and served no reachable flow — is removed; the grant lives solely in the key's own shared map.

New e2e: a chained call into a declaring app stays callable without receiving grants (the case the old guard broke).

Behavior changes flagged for review

  • Required-dependency hard-fail on chained hops is NOT here (that arrives with the chained-resolution PR later in the stack) — in this PR, hops under a per-request key are skipped, never failed.
  • Internal engine types (credentials, keys, …) are no longer resolvable via the placeholder, whoever authored the app.
  • Records sharing a target now union their grants (previously last-write-wins overwrite).

Checklist

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

🤖 Generated with Claude Code

Serguei Gorokhov and others added 2 commits September 3, 2026 01:02
… e2e suite

ResolveResourceDependenciesFn now runs in the same chain position on the
Anthropic Messages and Responses API entry points, completing the v1
mint-site coverage (route/feature/MCP/interceptor/deploy sites remain a
documented v1 limitation — an app invoked through those surfaces gets
no dependency grants in v1).

ResourceDependencyApiTest covers the v1 checklist as test cases: the
consented flow, required-without-consent failing the call explicitly,
optional-without-consent degrading silently (target stays off-limits),
withdrawal stopping the run immediately, declaration change invalidating
the grant (content binding), public-target grants following the
originating user's own reach exactly (pointer semantics, D-04), and the
audit events carrying app, targets, access types, user and trace id.

Spec: documentation repo, offline-access-delegation/implementation-specs/pr5-mint-sites-and-e2e.md

Co-Authored-By: Claude Code <noreply@anthropic.com>
…rants

Whole-branch review follow-up to the resolution commit — three real
defects and a defense-in-depth gap, all in the composed behavior:

- The timing guard threw IllegalStateException on every hop that arrives
  with a per-request key — which is the normal state for an interceptor's
  final call back to a declaring app (breaking even the root user
  request) and for every chained call into one (400/500 on all three
  interfaces). Resolution is now honestly root-call-only: hops under a
  per-request key are skipped — callable, just without grants — because
  the originating user's reach is not evaluable under a key context
  (buildUserBucket flips to the parent app's bucket). Chained
  composition (design §7.1) is recorded as phase-2 machinery.
- The perRequestReceivers[app] half of the bake never propagated on
  plain chained calls (child mints copy only their own receiver entry)
  and served no reachable flow — removed; the grant lives solely in the
  key's own shared map.
- The placeholder type segment now enforces the validator's
  PERSONAL_TYPED_ROOTS vocabulary (constant made public, shared):
  ResourceTypes.of() maps internal engine types, so a
  current-user/credentials/ declaration from a config-file or
  admin-authored app could otherwise resolve into the user's
  secret-bearing blobs.
- Grants bake with union semantics — two records sharing a target
  combine rights instead of overwriting each other; malformed concrete
  paths raise RuntimeException shapes that are now caught (unresolvable,
  never a crash); target permission lookups are batched into one
  rule-chain walk; consent is checked before any resolution work; the
  audit's access-type join tolerates null elements.

New e2e: a chained call into a declaring app stays callable without
receiving grants (the case the old guard broke).

Co-Authored-By: Claude Code <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant