Commit 648e81c
fix(schedules): grant legacy auth for run schedules (#344)
## Summary
- Preserve Spark-style schedule resource registration while also
granting the schedule directly for legacy SGP auth.
- Ensure schedule create rollback and delete cleanup revoke legacy
permissions as well as deregister Spark resources.
- Cover create, rollback, grant-failure compensation, no-creator, and
delete cleanup behavior in focused unit tests.
## Context
Legacy `agentex-auth` currently runs with `AUTH_PROVIDER=sgp`, where
`register_resource` is a no-op. Run schedules were only writing
Spark-style registration, so schedule rows and Temporal clocks could
exist while legacy `schedule.read` search/check returned no permission,
causing list endpoints to return `200 []`.
This bridges current legacy SGP auth while keeping `register_resource` /
`deregister_resource` in place for the eventual Spark migration.
## Test plan
- `uv run --group test pytest
tests/unit/services/test_agent_run_schedule_service.py`
- `uv run ruff check src/domain/services/agent_run_schedule_service.py
tests/unit/services/test_agent_run_schedule_service.py`
## Notes
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](https://cursor.com)
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR bridges the gap where `register_resource` is a no-op under the
legacy SGP auth provider (`AUTH_PROVIDER=sgp`), causing `schedule.read`
to return no permission and list endpoints to return `200 []`. It adds a
`grant` call after `register_resource` in the creation path and a
`revoke` call before `deregister_resource` in the cleanup path, keeping
both Spark-style and SGP-style auth in sync.
- `_register_schedule_in_auth` now calls `grant` after
`register_resource`, with a compensation block that calls
`deregister_resource` on grant failure before re-raising; the
`registered` flag in `create_schedule` remains `False` on any failure so
the outer rollback doesn't double-deregister.
- `_deregister_schedule_from_auth` now prepends a best-effort `revoke`
call (in its own try/except) before the existing `deregister_resource`,
so both auth paths are cleaned up on delete or rollback.
- Tests cover the happy path, grant-failure compensation,
registration-failure abort, Temporal-failure rollback, no-creator skip,
and delete cleanup — with precise `assert_called_once_with` assertions
on the new `grant` and `revoke` calls.
<details><summary><h3>Confidence Score: 5/5</h3></summary>
Safe to merge — the dual-auth bridge is correctly wired, the
`registered` flag prevents double-cleanup, and the new test cases verify
all compensation paths.
The rollback semantics are sound: `registered` stays `False` if
`_register_schedule_in_auth` raises (whether from `register_resource` or
`grant`), so the outer cleanup never calls
`_deregister_schedule_from_auth` when the inner compensation already
handled it. The grant-failure path correctly skips `revoke` (nothing was
granted). The delete path calls `revoke` then `deregister_resource` in
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.
</details>
<details><summary><h3>Important Files Changed</h3></summary>
| Filename | Overview |
|----------|----------|
| agentex/src/domain/services/agent_run_schedule_service.py | Adds
legacy SGP grant/revoke calls alongside the existing Spark
register/deregister calls; compensation logic and rollback flag
semantics are correct — grant failure triggers deregister (not revoke),
and the `registered` flag stays False so the outer rollback never
double-cleans. |
| agentex/tests/unit/services/test_agent_run_schedule_service.py | New
tests cover grant-failure compensation, no-creator skip, and upgraded
assertions on existing rollback/delete tests to use
`assert_called_once_with` for both `grant` and `revoke`; all scenarios
are consistent with implementation behavior. |
</details>
<details><summary><h3>Sequence Diagram</h3></summary>
<a href="#gh-light-mode-only">
```mermaid
%%{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
```
</a>
<a href="#gh-dark-mode-only">
```mermaid
%%{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
end
```
</a>
</details>
<sub>Reviews (2): Last reviewed commit: ["test(schedules): tighten
legacy auth
cle..."](7202557)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=41188765)</sub>
<!-- /greptile_comment -->
---------
Co-authored-by: Cursor <cursoragent@cursor.com>1 parent 4c1740b commit 648e81c
2 files changed
Lines changed: 98 additions & 5 deletions
File tree
- agentex
- src/domain/services
- tests/unit/services
Lines changed: 43 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
502 | 502 | | |
503 | 503 | | |
504 | 504 | | |
| 505 | + | |
505 | 506 | | |
506 | | - | |
| 507 | + | |
507 | 508 | | |
508 | 509 | | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
509 | 538 | | |
510 | 539 | | |
511 | 540 | | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
512 | 553 | | |
513 | 554 | | |
514 | | - | |
| 555 | + | |
515 | 556 | | |
516 | 557 | | |
517 | 558 | | |
| |||
Lines changed: 55 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| |||
112 | 113 | | |
113 | 114 | | |
114 | 115 | | |
115 | | - | |
116 | | - | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
117 | 126 | | |
118 | 127 | | |
119 | 128 | | |
| |||
141 | 150 | | |
142 | 151 | | |
143 | 152 | | |
144 | | - | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
145 | 156 | | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
146 | 160 | | |
147 | 161 | | |
148 | 162 | | |
| |||
163 | 177 | | |
164 | 178 | | |
165 | 179 | | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
166 | 213 | | |
167 | 214 | | |
168 | 215 | | |
| |||
255 | 302 | | |
256 | 303 | | |
257 | 304 | | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
258 | 310 | | |
259 | 311 | | |
260 | 312 | | |
| |||
0 commit comments