feat(schedules): register agent_schedule in authz on create/delete#259
Open
harvhan wants to merge 1 commit into
Open
feat(schedules): register agent_schedule in authz on create/delete#259harvhan wants to merge 1 commit into
harvhan wants to merge 1 commit into
Conversation
Register an agent_schedule resource (with the parent_agent edge) in the authorization service before the Temporal create, and deregister it on delete. The register/deregister calls live in ScheduleService, alongside the Temporal write, so the compensation boundary can be scoped to the Temporal create only: registration is unconditional and fail-closed; a Temporal create failure after a successful register triggers a compensating deregister so no orphan tuple is left behind. A post-create read-back (describe) failure does NOT compensate, since the schedule was actually created. Deregister on delete is best-effort. Registration is skipped with a warning when no creator identity is resolvable on the principal context (agent-bypass / internal paths). Mirrors the agent_api_key register/deregister pattern. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Summary
Registers an
agent_scheduleresource in the authorization service on schedulecreate (with the owning agent as its parent edge) and deregisters it on delete,
so schedules participate in fine-grained authorization.
Schedules have no Postgres row — Temporal is the store and the authz selector is
the schedule id (
{agent_id}--{schedule_name}). The register/deregister callstherefore live in
ScheduleService, alongside the Temporal write, so thecompensation boundary can be scoped to the Temporal create only.
Behavior
parent=agent) before theTemporal create. Unconditional and fail-closed — a register failure
aborts the create. If the Temporal create fails after a successful register,
a compensating deregister removes the tuple so no orphan is left. The
post-create read-back (describe) is outside the compensation scope: a describe
failure must not deregister a schedule that was actually created.
never blocks the delete).
nor service-account identity): registration is skipped with a warning and the
schedule is still created. Interim behavior until on-behalf-of-user identity
is threaded.
Per-account routing (which authz backend the call lands on) is owned by the
authorization service; this repo calls register/deregister unconditionally.
Follows the
agent_api_keyregister/deregister pattern (#248), which this PR isstacked on.
Requires
A companion change in the authorization service mapping the
scheduleresourcetype to its SpiceDB
agent_scheduledefinition; without it theregister/deregister call is rejected before reaching the backend.
Changes
schedule_service.py: register-before-create + compensate-on-temporal-failure;best-effort deregister on delete;
_register_schedule_in_auth/_deregister_schedule_from_authhelpers; inject the authorization service.authorization_types.py: add thescheduleresource type + factory.tests/integration/services/test_schedule_service_dual_write.py: 7 cases(parent-edge contract, fail-closed create, compensating deregister on Temporal
failure, read-back failure does NOT compensate, best-effort deregister on
delete, no-creator skip).
tests/unit/services/test_schedule_service.py: fixture updated for the newconstructor arg.
Test plan
pytest tests/integration/services/test_schedule_service_dual_write.py— 7/7pytest tests/unit/services/test_schedule_service.py— 27/27pytest tests/unit/use_cases/test_schedules_use_case.py— 18/18Stacked on
#248 (base branch). Rebase onto the default branch once #248 merges.
🤖 Generated with Claude Code
Greptile Summary
This PR registers
agent_scheduleresources in the authorization service on schedule create and deregisters them on delete, following the same pattern established foragent_api_keyin #248. Because schedules have no Postgres row (Temporal is the store), the auth dual-write lives entirely inScheduleServicealongside the Temporal write.schedule_service.py: InjectsAuthorizationService, adds_register_schedule_in_auth(fail-closed, called before the Temporal create) and_deregister_schedule_from_auth(best-effort, never raises); compensating deregister on Temporal failure and best-effort deregister on delete are correctly wired. The no-creator skip path is intentionally left as interim behavior.authorization_types.py: AddsscheduletoAgentexResourceTypeand factory methods onAgentexResource/AgentexResourceOptionalSelector, mirroring theapi_keypattern exactly.Confidence Score: 4/5
Safe to merge; the core register/compensate/deregister logic is correct and well-tested, with one minor documentation gap.
The implementation faithfully follows the agent_api_key pattern, the fail-closed + compensating-deregister design is correctly scoped (read-back failures intentionally outside the compensation boundary), and all seven stated behavioral guarantees are covered by dedicated tests. The only open item is that the interim-behavior note in the docstring has no linked Linear ticket, which could cause that follow-up to slip through.
schedule_service.pyis the only file worth a close look — specifically the_register_schedule_in_authdocstring's untracked interim note and the interaction between theuser_id/service_account_idearly-exit and the_bypass()logic insideAuthorizationService.Important Files Changed
scheduleto theAgentexResourceTypeenum and corresponding factory class methods onAgentexResourceandAgentexResourceOptionalSelector; follows the existingapi_keypattern exactly.authorization_servicemock with a resolvable principal; no test logic changed beyond the constructor update.Sequence Diagram
sequenceDiagram participant C as Caller participant SS as ScheduleService participant AS as AuthorizationService participant TA as TemporalAdapter C->>SS: create_schedule(agent, request) SS->>SS: build_schedule_id() SS->>SS: _register_schedule_in_auth() alt no creator identity SS-->>SS: log warning, return False else creator resolvable SS->>AS: "register_resource(schedule, parent=agent)" alt register fails AS-->>SS: Exception SS-->>C: raise (fail-closed, Temporal NOT called) else register succeeds AS-->>SS: "OK (registered=True)" end end SS->>TA: create_schedule(...) alt Temporal create fails TA-->>SS: Exception alt "registered=True" SS->>AS: deregister_resource(schedule) [best-effort] end SS-->>C: raise original exception else Temporal create succeeds TA-->>SS: OK SS->>SS: get_schedule() [read-back, outside compensation scope] SS-->>C: ScheduleResponse end C->>SS: delete_schedule(agent_id, schedule_name) SS->>TA: delete_schedule(schedule_id) TA-->>SS: OK SS->>AS: deregister_resource(schedule) [best-effort] AS-->>SS: OK or error (swallowed) SS-->>C: NonePrompt To Fix All With AI
Reviews (1): Last reviewed commit: "feat(schedules): register agent_schedule..." | Re-trigger Greptile
Context used:
Learned From
scaleapi/scaleapi#126926