Skip to content

feat: register_resource with parent edge for agent_api_keys dual-write#253

Closed
dm36 wants to merge 1 commit into
dhruv/agx1-272-agent-api-keys-dual-writefrom
dhruv/scale-agentex-parent-relation
Closed

feat: register_resource with parent edge for agent_api_keys dual-write#253
dm36 wants to merge 1 commit into
dhruv/agx1-272-agent-api-keys-dual-writefrom
dhruv/scale-agentex-parent-relation

Conversation

@dm36
Copy link
Copy Markdown

@dm36 dm36 commented May 26, 2026

Summary

Closes the `parent_agent` cascade gap surfaced on scaleapi/agentex#354. Stacked on PR #248 (AGX1-272 dual-write).

The api_key dual-write currently calls `authorization_service.grant(api_key)` which writes the owner edge in SpiceDB but NOT the `parent_agent` edge. The `agent_api_key` schema requires:

```
permission read = internal_effective_viewer & parent_agent->read & internal_tenant_gate
permission update = internal_effective_editor & parent_agent->update & internal_tenant_gate
```

Without the `parent_agent` edge, every downstream read/update fails closed — even for the owner.

This PR adds `register_resource` / `deregister_resource` methods and swaps the api_keys use case to write the parent edge atomically.

Dependency chain

This is PR #3 of 3 in a cross-repo coordinated change. Must merge in order:

# Repo PR State
1 scaleapi/scaleapi #144657 — sgp-authz 0.6.0 → 0.7.0 with `parent_resource` kwarg Draft
2 scaleapi/agentex #355 — agentex-auth Port + adapter + HTTP routes Draft
3 (this PR) scaleapi/scale-agentex this scale-agentex Port + use case swap
Base scaleapi/scale-agentex #248 — AGX1-272 dual-write (this PR stacks on top) Draft

Changes

  • `src/adapters/authorization/port.py` — abstract `register_resource(resource, parent=None)` and `deregister_resource(resource)` on `AuthorizationGateway`.
  • `src/adapters/authorization/adapter_agentex_authz_proxy.py` — POST `/v1/authz/register` and `/v1/authz/deregister` to agentex-auth.
  • `src/domain/services/authorization_service.py` — `register_resource` and `deregister_resource` service methods mirroring the existing grant/revoke pattern (`principal_context` override, `_bypass` support, parent shown in log line for cascade debugging).
  • `src/domain/use_cases/agent_api_keys_use_case.py` —
    • Swap `grant → register_resource` in `_register_api_key_in_spark_authz`, passing `parent=AgentexResource.agent(agent_id)`.
    • Swap `revoke → deregister_resource` in `_deregister_api_key_from_spark_authz`.
    • `except Exception` wrappers preserved: fail-closed on register, best-effort on deregister.
  • `tests/integration/services/test_agent_api_key_service_dual_write.py` — rename mocks to `register_resource`/`deregister_resource`; add explicit assertion that the `parent` edge is passed correctly (the load-bearing part — this is what prevents the cascade from failing closed).

5 files, +215/-66.

Test plan

Why this stacks on #248

The use-case change in `_register_api_key_in_spark_authz` directly depends on PR #248's existing code structure (the method, the feature flag check, the creator metadata population). Building on top of #248 makes the diff small and the intent obvious.

If #248 changes during review, this PR rebases mechanically. If the team prefers to fold the parent-edge work into #248 directly (one PR instead of two), happy to do that too.

Out of scope

Closes the parent_agent cascade gap surfaced on scaleapi/agentex#354.
The api_key dual-write (AGX1-272, PR #248) currently calls grant() which
writes the owner edge in SpiceDB but NOT the parent_agent edge. The
agent_api_key schema requires `read = ... & parent_agent->read & ...`,
so every downstream read/update fails closed without that edge.

This PR adds register_resource/deregister_resource (Port + adapter + service)
and swaps the api_keys use case from grant→register_resource with
parent=AgentexResource.agent(agent_id). Now the owner edge and parent_agent
edge are written atomically.

Stack:
- scaleapi/scaleapi#144657 — sgp-authz 0.7.0 (parent_resource kwarg).
- scaleapi/agentex#355 — agentex-auth Port + adapter + HTTP routes.
- #248 — original AGX1-272 dual-write (this stacks on it).
- THIS PR — extends #248 to use the parent-aware path.

Changes:
- Port: abstract register_resource(resource, parent=None) and
  deregister_resource(resource).
- Adapter proxy: POST /v1/authz/register and /v1/authz/deregister.
- Service: mirror existing grant/revoke pattern (principal_context override,
  _bypass support, parent in log line for cascade debugging).
- Use case: swap grant→register_resource passing parent=agent;
  swap revoke→deregister_resource. except Exception wrappers preserved
  (fail-closed on register, best-effort on deregister).
- Tests: rename mocks to register_resource/deregister_resource; assert the
  parent edge is passed correctly.

Test plan:
- pytest agentex/tests/integration/services/test_agent_api_key_service_dual_write.py
  → 8 / 8 pass.
- New test ``test_create_api_key_calls_grant_when_flag_on`` asserts
  parent.type == AgentexResourceType.agent and parent.selector == agent.id.

Other resource types' grant→register_resource swap is out of scope.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@dm36
Copy link
Copy Markdown
Author

dm36 commented May 26, 2026

Closing — work consolidated into #248 via cherry-pick. See #248 for the combined PR (dual-write infrastructure + parent-edge work). Branch left in place for history reference.

@dm36 dm36 closed this May 26, 2026
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