fix(schedules): grant legacy auth for run schedules#344
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Implementation note: this is intentionally a bridge for the current legacy SGP auth path. Once Spark resource registration is the active read/write path for AgentEx resources, we should revisit this dual-write and remove the legacy grant/revoke fallback if it is no longer needed. Existing schedules created before this change may still need a one-time legacy grant backfill or recreation. |
| ) | ||
| return False | ||
| schedule_resource = AgentexResource.schedule(authz_selector) | ||
| await self.authorization_service.register_resource( |
There was a problem hiding this comment.
Shouldnt we be checking spark authz feature flag for this?
There was a problem hiding this comment.
Good question. I checked and those Spark rollout flags seem to live in agentex-auth, not scale-agentex.
My understanding is:
fgac-agentex-resourcescontrols read/check/search routing.fgac-agentex-resources-dual-writecontrols whetherregister_resourcewrites to Spark.
Since scale-agentex doesn’t currently evaluate those flags, I kept this as a local compatibility bridge for legacy SGP. Do you recommend changing anything here, or does this seem reasonable while legacy SGP is still active?
There was a problem hiding this comment.
Gotcha no seems reasonable
Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
Context
Legacy
agentex-authcurrently runs withAUTH_PROVIDER=sgp, whereregister_resourceis a no-op. Run schedules were only writing Spark-style registration, so schedule rows and Temporal clocks could exist while legacyschedule.readsearch/check returned no permission, causing list endpoints to return200 [].This bridges current legacy SGP auth while keeping
register_resource/deregister_resourcein place for the eventual Spark migration.Test plan
uv run --group test pytest tests/unit/services/test_agent_run_schedule_service.pyuv run ruff check src/domain/services/agent_run_schedule_service.py tests/unit/services/test_agent_run_schedule_service.pyNotes
Existing schedules created before this change may need a one-time legacy grant backfill or recreation to become visible through schedule list/get under legacy SGP auth.
Made with Cursor
Greptile Summary
This PR bridges the gap where
register_resourceis a no-op under the legacy SGP auth provider (AUTH_PROVIDER=sgp), causingschedule.readto return no permission and list endpoints to return200 []. It adds agrantcall afterregister_resourcein the creation path and arevokecall beforederegister_resourcein the cleanup path, keeping both Spark-style and SGP-style auth in sync._register_schedule_in_authnow callsgrantafterregister_resource, with a compensation block that callsderegister_resourceon grant failure before re-raising; theregisteredflag increate_scheduleremainsFalseon any failure so the outer rollback doesn't double-deregister._deregister_schedule_from_authnow prepends a best-effortrevokecall (in its own try/except) before the existingderegister_resource, so both auth paths are cleaned up on delete or rollback.assert_called_once_withassertions on the newgrantandrevokecalls.Confidence Score: 5/5
Safe to merge — the dual-auth bridge is correctly wired, the
registeredflag prevents double-cleanup, and the new test cases verify all compensation paths.The rollback semantics are sound:
registeredstaysFalseif_register_schedule_in_authraises (whether fromregister_resourceorgrant), so the outer cleanup never calls_deregister_schedule_from_authwhen the inner compensation already handled it. The grant-failure path correctly skipsrevoke(nothing was granted). The delete path callsrevokethenderegister_resourcein independent try/except blocks, so a failure in one doesn't prevent the other. Tests are precise and cover the newly introduced failure modes.No files require special attention.
Important Files Changed
registeredflag stays False so the outer rollback never double-cleans.assert_called_once_withfor bothgrantandrevoke; all scenarios are consistent with implementation behavior.Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant CS as create_schedule participant RA as _register_schedule_in_auth participant AS as authorization_service participant TA as temporal_adapter participant DR as _deregister_schedule_from_auth CS->>RA: _register_schedule_in_auth(authz_selector, agent_id) RA->>AS: "register_resource(schedule_resource, parent=agent_resource)" alt register_resource fails AS-->>RA: raises Exception RA-->>CS: "raises (registered=False)" CS->>CS: _best_effort_delete_row() else register_resource succeeds RA->>AS: grant(schedule_resource) alt grant fails AS-->>RA: raises Exception RA->>AS: deregister_resource(schedule_resource) [compensation] RA-->>CS: "raises (registered=False)" CS->>CS: _best_effort_delete_row() else grant succeeds RA-->>CS: "returns True (registered=True)" CS->>TA: create_schedule(...) alt Temporal fails TA-->>CS: raises Exception CS->>DR: _deregister_schedule_from_auth(authz_selector) DR->>AS: revoke(schedule_resource) DR->>AS: deregister_resource(schedule_resource) CS->>CS: _best_effort_delete_row() else Temporal succeeds TA-->>CS: success CS-->>CS: return response end end end%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant CS as create_schedule participant RA as _register_schedule_in_auth participant AS as authorization_service participant TA as temporal_adapter participant DR as _deregister_schedule_from_auth CS->>RA: _register_schedule_in_auth(authz_selector, agent_id) RA->>AS: "register_resource(schedule_resource, parent=agent_resource)" alt register_resource fails AS-->>RA: raises Exception RA-->>CS: "raises (registered=False)" CS->>CS: _best_effort_delete_row() else register_resource succeeds RA->>AS: grant(schedule_resource) alt grant fails AS-->>RA: raises Exception RA->>AS: deregister_resource(schedule_resource) [compensation] RA-->>CS: "raises (registered=False)" CS->>CS: _best_effort_delete_row() else grant succeeds RA-->>CS: "returns True (registered=True)" CS->>TA: create_schedule(...) alt Temporal fails TA-->>CS: raises Exception CS->>DR: _deregister_schedule_from_auth(authz_selector) DR->>AS: revoke(schedule_resource) DR->>AS: deregister_resource(schedule_resource) CS->>CS: _best_effort_delete_row() else Temporal succeeds TA-->>CS: success CS-->>CS: return response end end endReviews (2): Last reviewed commit: "test(schedules): tighten legacy auth cle..." | Re-trigger Greptile