mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-13 08:02:47 +00:00
* refactor: make the app policy's principal the authority for its identity * fix: align the app backfill with the sibling migration and audit the uncached address * chore: refresh the sqlx cache after rebasing onto the merged base * fix: resolve the app execution address uncached, it decides the job's authorization * chore: cache the EE queries at the ref this branch pins * chore: cache the EE queries at the ref this branch pins * fix: derive the app draft's on-behalf-of address on read * chore: cache the query the draft derivation test added * fix: derive the app identity on the draft-table and version reads too * docs: state the draft resolver's authorization contract * fix: resolve a draft's principal against workspace membership only * chore: cache the membership lookup the draft resolver added * fix: drop an unresolvable draft's address instead of leaving it stale * perf: evict the address cache on change so app dispatch can read it * fix: evict on superadmin role changes, not only address changes * refactor: make the app policy's address optional instead of derived on read * fix: follow an external superadmin's rename into the apps that name them * docs: state the removal gate once, and correctly * refactor: drop the app-policy version constant that gated nothing * docs: drop the last reference to the removed constant * perf: read the address cache everywhere now that eviction reaches every replica * fix: keep persisted addresses off the cache the poller evicts asynchronously * docs: state where the cached address is accepted and where it is not * docs: keep the cache rule in one place and drop the stale premise * docs: sort the two lookups by how long a wrong answer lives * fix: resolve the schedule address uncached where it is written to the row * docs: name the release this actually ships in * perf: evict a superadmin's key per workspace instead of the whole cache * fix: evict every alias a superadmin principal can be spelled as * docs: describe the trigger as it is * docs: cover the round-tripped read in the cache rule * docs: record why a stale dispatch address cannot escalate * fix: validate a dispatch address against the principal's live binding * fix: carry the validated address through to the job row and token * fix: record the validated address on the job row, not the one handed in * test: run the substep tag check as the non-superadmin it means to test Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * fix: rewrite a stored app address that disagrees with its principal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * docs: record the accepted staleness window of the cached dispatch address Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * fix: record the validated address on the job's audit row Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * docs: record the accepted rename race of pre-transaction identity resolution Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * docs: separate the app's stored address from the derived one in the resolver doc Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * docs: describe the job identity fast path the push comments skipped Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * fix: backfill a legacy group-prefixed username as the group it names Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * fix: resolve a schedule edit's identity before opening its transaction Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * fix: never resolve a disabled member to a same-named superadmin Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * docs: state what the email-change notify buys, and rewrap two comment lines Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * fix: keep a group's runnables when offboarding a legacy group-prefixed member Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * fix: read the app author from the stored address, as execution does Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * docs: record the rename race's full consequence as a known, accepted limitation Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * docs: record the keep-target group address case as a known, accepted limitation Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
48 lines
2.6 KiB
Markdown
48 lines
2.6 KiB
Markdown
# Removing `policy.on_behalf_of_email`
|
|
|
|
An app's identity is `policy.on_behalf_of`; the address beside it is a function of that
|
|
principal. `on_behalf_of_email` is no longer required — a policy carrying only a principal
|
|
executes, deriving the address — but it is still written on every save, and that is the only
|
|
thing holding it in place.
|
|
|
|
## The gate
|
|
|
|
`get_on_behalf_of` gained its derive-when-absent fallback in **1.810**. Every replica before that
|
|
*requires* the key and errors outright without it, so it would 400 every anonymous, publisher and
|
|
guest app saved by a newer one. A rolling deploy runs both versions at once, which is why the
|
|
write stays until no replica older than 1.810 can be live — in practice, once
|
|
`MIN_KEEP_ALIVE_VERSION` (`windmill-common/src/min_version.rs`) has passed it.
|
|
|
|
There is no `MIN_VERSION_*` constant for this and it does not need one: those exist to gate
|
|
behavior at runtime or to trip the build when a constraint expires, and nothing here does either.
|
|
The key is written unconditionally, so no replica ever meets its absence until someone follows
|
|
the steps below.
|
|
|
|
## Step 1 — stop writing the key
|
|
|
|
- `stored_on_behalf_of_email` in `windmill-api/src/apps.rs`, and the `create_app` /
|
|
`update_app_internal` call sites that store what it returns.
|
|
- The CLI and frontend workspace-deploy paths (`cli/windmill-utils-internal/src/deploy.ts`,
|
|
`frontend/src/lib/utils_workspace_deploy.ts`). These send the address *instead of* a principal
|
|
for a cross-workspace deploy, which is the one case where it is the only identity available —
|
|
so this is "stop sending it once the target resolves a principal itself", not a deletion.
|
|
|
|
Policies written before this keep their key and keep being read from it; they agree with their
|
|
principal, so nothing has to strip them.
|
|
|
|
## Step 2 — drop the field
|
|
|
|
Remove `on_behalf_of_email` from `Policy` and the fallback in `get_on_behalf_of`, which then
|
|
always derives. Optionally strip the key from stored policies.
|
|
|
|
This can ship with step 1. It is written separately because step 1 alone is revertible without
|
|
touching stored data or the response schema, and because the gate above is what makes either
|
|
step safe — nothing about step 2 needs its own waiting period.
|
|
|
|
## Why the address is not derived on read
|
|
|
|
Read paths return the stored address verbatim rather than recomputing it. Deriving on read means
|
|
resolving a principal that, for a draft, is caller-controlled — which turns the read into an
|
|
oracle for addresses the caller cannot otherwise see, and leaves a principal that resolves to
|
|
nobody with no address at all. Both were live defects while the read paths did derive.
|