Skip to content

Add typed brokered credential contract - #84

Merged
divo12 merged 2 commits into
mainfrom
feat/credential-brokerage
Aug 23, 2026
Merged

divo12 merged 2 commits into
mainfrom
feat/credential-brokerage

Conversation

@divo12

@divo12 divo12 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

User description

Summary

  • add the typed dream.contracts.credentials seam with enums, grant/ask/lease objects, and no plaintext fields
  • expose the broker through ToolContext and session construction
  • add TDD coverage for validation and context wiring

Verification

  • uv run pytest -q tests/test_contracts/test_credentials.py tests/test_tools/test_context.py tests/test_factory.py tests/test_public_api.py

Chorus depends on this contract bump.


CodeAnt-AI Description

Add a typed brokered credential contract for tools

What Changed

  • Tools can request credentials through a broker without receiving plaintext secret values
  • Credential access supports one-time or standing grants, approval requests, revocation, usage tracking, and opaque session leases
  • Credentials can be delivered through controlled proxy requests or environment injection with host, path, method, and header restrictions
  • Applications can provide a credential broker when building a harness, and tools receive it through their execution context
  • Invalid credential names, owners, purposes, delivery settings, and path restrictions are rejected

Impact

✅ No plaintext credentials in tool-facing contracts
✅ Controlled credential access with approval and revocation
✅ Tools can use brokered credentials through proxy or environment delivery

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@codeant-ai

codeant-ai Bot commented Aug 2, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Incremental review completed 16b2fdb Aug 23, 2026 · 07:12 07:12
✅ Reviewed your PR 73e379c Aug 02, 2026 · 04:20 04:23

@codeant-ai

codeant-ai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Aug 2, 2026
Comment on lines +173 to +175
session: CredentialSession
delivery: CredentialDelivery
opaque_handle: str

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: CredentialLease does not enforce a consistent delivery shape: an environment lease can omit env_key, while a broker lease can carry one. Downstream materialization or injection can therefore receive an unusable lease or route credential delivery through the wrong mechanism. Validate the delivery/env_key combination in __post_init__, matching the request invariants. [security]

Severity Level: Major ⚠️
- ⚠️ Host broker implementations receive unusable lease metadata.
- ⚠️ Credential proxy or environment delivery can fail validation.
- ⚠️ Each consumer must duplicate request delivery invariants.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/dream/contracts/credentials.py
**Line:** 173:175
**Comment:**
	*Security: `CredentialLease` does not enforce a consistent delivery shape: an environment lease can omit `env_key`, while a broker lease can carry one. Downstream materialization or injection can therefore receive an unusable lease or route credential delivery through the wrong mechanism. Validate the `delivery`/`env_key` combination in `__post_init__`, matching the request invariants.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +180 to +184
class CredentialRequestResult:
status: CredentialRequestStatus
grant: CredentialGrant | None = None
ask: CredentialAsk | None = None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: CredentialRequestResult permits structurally invalid results such as GRANTED with no grant or APPROVAL_REQUIRED with no ask. Consumers must then dereference a missing payload or add repeated status-specific checks, defeating the typed broker contract. Validate the required payload for each status and reject the contradictory payload combinations. [api mismatch]

Severity Level: Major ⚠️
- ⚠️ Credential acquisition requires repeated payload checks.
- ❌ Malformed broker results can fail grant materialization.
- ⚠️ Approval flows can lack an actionable ask identifier.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/dream/contracts/credentials.py
**Line:** 180:184
**Comment:**
	*Api Mismatch: `CredentialRequestResult` permits structurally invalid results such as `GRANTED` with no `grant` or `APPROVAL_REQUIRED` with no `ask`. Consumers must then dereference a missing payload or add repeated status-specific checks, defeating the typed broker contract. Validate the required payload for each status and reject the contradictory payload combinations.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +48 to +49
@property
def credential_broker(self) -> CredentialBrokerPort | None: ...

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Adding credential_broker as a required member of the runtime-checkable ToolContext protocol breaks structural compatibility for every existing external context implementation that follows the previous contract. Such implementations will no longer satisfy isinstance(context, ToolContext) or static protocol checks even when credential access is unused. Preserve compatibility with an optional extension protocol or otherwise coordinate this as a breaking contract change rather than an additive one. [api mismatch]

Severity Level: Major ⚠️
- ❌ Existing external context adapters fail protocol checks.
- ⚠️ Third-party tools may require coordinated updates.
- ⚠️ Compatibility breaks despite the broker being optional at runtime.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/dream/contracts/tool.py
**Line:** 48:49
**Comment:**
	*Api Mismatch: Adding `credential_broker` as a required member of the runtime-checkable `ToolContext` protocol breaks structural compatibility for every existing external context implementation that follows the previous contract. Such implementations will no longer satisfy `isinstance(context, ToolContext)` or static protocol checks even when credential access is unused. Preserve compatibility with an optional extension protocol or otherwise coordinate this as a breaking contract change rather than an additive one.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@divo12

divo12 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai please review

@greptile-apps

greptile-apps Bot commented Aug 8, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds a public brokered-credential contract and threads an optional broker from harness construction into tool execution contexts.

  • Defines typed requests, grants, approval asks, leases, delivery modes, proxy operations, and environment injection.
  • Exports the contract as version 0.7.0.
  • Adds broker propagation through the session engine and dispatcher.
  • Adds validation and context-field tests, though the complete wiring path remains untested.

Confidence Score: 4/5

The PR appears safe to merge, with non-blocking improvements recommended for result-state invariants and end-to-end broker-wiring coverage.

The production broker path is consistently forwarded into tool contexts, but the public result type admits contradictory status and payload combinations and the tests do not protect the full forwarding chain.

Files Needing Attention: src/dream/contracts/credentials.py, tests/test_tools/test_context.py

Important Files Changed

Filename Overview
src/dream/contracts/credentials.py Adds the credential-broker contract; the request result does not enforce consistency between its discriminator and optional payloads.
src/dream/_factory.py Accepts the optional broker and correctly captures it for session-engine construction.
src/dream/engine/_engine.py Correctly forwards and retains the broker on the dispatcher and query engine.
src/dream/engine/_tool_dispatch.py Correctly places the configured broker into each ToolExecutionContext.
src/dream/contracts/tool.py Adds the optional broker property to the public ToolContext protocol.
tests/test_tools/test_context.py Verifies the destination context field directly but does not cover the newly added production forwarding chain.

Sequence Diagram

sequenceDiagram
    participant App
    participant Harness as build_harness
    participant Session as _build_session_engine
    participant Engine as build_query_engine
    participant Dispatcher as EngineToolDispatcher
    participant Tool as ToolExecutionContext
    App->>Harness: credential_broker
    Harness->>Session: credential_broker
    Session->>Engine: credential_broker
    Engine->>Dispatcher: credential_broker
    Dispatcher->>Tool: credential_broker
    Tool-->>App: broker available to tool
Loading
Prompt To Fix All With AI
### Issue 1
src/dream/contracts/credentials.py:179-183
**Result payloads allow contradictory states**

`CredentialRequestResult` permits `GRANTED` without a grant, `APPROVAL_REQUIRED` without an ask, or both payloads at once. Consumers selecting a payload from `status` can therefore dereference `None` or process the wrong payload, so the result should enforce exactly one status-appropriate value.

```suggestion
@dataclass(frozen=True)
class CredentialRequestResult:
    status: CredentialRequestStatus
    grant: CredentialGrant | None = None
    ask: CredentialAsk | None = None

    def __post_init__(self) -> None:
        if self.status is CredentialRequestStatus.GRANTED:
            if self.grant is None or self.ask is not None:
                raise ValueError("granted result requires only a grant")
        elif self.status is CredentialRequestStatus.APPROVAL_REQUIRED:
            if self.ask is None or self.grant is not None:
                raise ValueError("approval-required result requires only an ask")
```

### Issue 2
tests/test_tools/test_context.py:35-42
**Broker wiring remains untested**

This test constructs `ToolExecutionContext` directly, bypassing the newly added forwarding chain from `build_harness` through the session engine and dispatcher. A regression that drops the broker at any intermediate hop would leave tools with `credential_broker=None` while this test still passes; add coverage that dispatches a tool through a broker-configured harness.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "add typed brokered credential contract" | Re-trigger Greptile

Comment on lines +179 to +183
@dataclass(frozen=True)
class CredentialRequestResult:
status: CredentialRequestStatus
grant: CredentialGrant | None = None
ask: CredentialAsk | None = None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Result payloads allow contradictory states

CredentialRequestResult permits GRANTED without a grant, APPROVAL_REQUIRED without an ask, or both payloads at once. Consumers selecting a payload from status can therefore dereference None or process the wrong payload, so the result should enforce exactly one status-appropriate value.

Suggested change
@dataclass(frozen=True)
class CredentialRequestResult:
status: CredentialRequestStatus
grant: CredentialGrant | None = None
ask: CredentialAsk | None = None
@dataclass(frozen=True)
class CredentialRequestResult:
status: CredentialRequestStatus
grant: CredentialGrant | None = None
ask: CredentialAsk | None = None
def __post_init__(self) -> None:
if self.status is CredentialRequestStatus.GRANTED:
if self.grant is None or self.ask is not None:
raise ValueError("granted result requires only a grant")
elif self.status is CredentialRequestStatus.APPROVAL_REQUIRED:
if self.ask is None or self.grant is not None:
raise ValueError("approval-required result requires only an ask")
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/dream/contracts/credentials.py
Line: 179-183

Comment:
**Result payloads allow contradictory states**

`CredentialRequestResult` permits `GRANTED` without a grant, `APPROVAL_REQUIRED` without an ask, or both payloads at once. Consumers selecting a payload from `status` can therefore dereference `None` or process the wrong payload, so the result should enforce exactly one status-appropriate value.

```suggestion
@dataclass(frozen=True)
class CredentialRequestResult:
    status: CredentialRequestStatus
    grant: CredentialGrant | None = None
    ask: CredentialAsk | None = None

    def __post_init__(self) -> None:
        if self.status is CredentialRequestStatus.GRANTED:
            if self.grant is None or self.ask is not None:
                raise ValueError("granted result requires only a grant")
        elif self.status is CredentialRequestStatus.APPROVAL_REQUIRED:
            if self.ask is None or self.grant is not None:
                raise ValueError("approval-required result requires only an ask")
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +35 to +42
def test_context_carries_typed_credential_broker(tmp_path: Path) -> None:
broker = cast(CredentialBrokerPort, object())
ctx = ToolExecutionContext(
working_dir=tmp_path,
session_id="session",
credential_broker=broker,
)
assert ctx.credential_broker is broker

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Broker wiring remains untested

This test constructs ToolExecutionContext directly, bypassing the newly added forwarding chain from build_harness through the session engine and dispatcher. A regression that drops the broker at any intermediate hop would leave tools with credential_broker=None while this test still passes; add coverage that dispatches a tool through a broker-configured harness.

Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/test_tools/test_context.py
Line: 35-42

Comment:
**Broker wiring remains untested**

This test constructs `ToolExecutionContext` directly, bypassing the newly added forwarding chain from `build_harness` through the session engine and dispatcher. A regression that drops the broker at any intermediate hop would leave tools with `credential_broker=None` while this test still passes; add coverage that dispatches a tool through a broker-configured harness.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@divo12
divo12 merged commit 1e5d7c0 into main Aug 23, 2026
@codeant-ai

codeant-ai Bot commented Aug 23, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@codeant-ai codeant-ai Bot added size:L This PR changes 100-499 lines, ignoring generated files and removed size:L This PR changes 100-499 lines, ignoring generated files labels Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant