fix(work): the ExecPlan projection is workspace-scoped, not tenant-scoped - #743
Merged
Merged
Conversation
…oped `a413ce6f` (#686, the red-steel replay) bound /v1/work to the caller's authenticated tenant so one tenant's kanban rows could not be counted into another's roll-up. That is right for kanban. It was applied to the ExecPlan projection too, which has no tenant dimension at all: `list_execplans` walks plan *files* under one root and stamps every item `tenant_id: None`, so `work_tenant_id(w) == tenant_id` compared "default" against the caller's tenant and dropped every plan. The failure is silent. The board answers `200 {"count":0}`, which is indistinguishable from an empty backlog or an unconfigured root, so any token whose tenant claim is not `default` sees no work at all — on /v1/work and on /v1/attention/summary, which calls the same helper. Observed on host crux v0.5.62 with an agent token scoped to tenant `work`: source=execplans returned count 0 while ?tenant_id=default returned 403 TENANT_FORBIDDEN. The same host listed 42 plans at v0.5.59, before this commit shipped. Filter only the items that actually declare a tenant. Workspace-scoped plans (`tenant_id: None`) are visible to any authorized reader — the same argument the code already makes one line above for `project_id` — while a future tenant-stamped plan stays narrowed. Kanban binding is untouched. Split the narrowing into `filter_execplan_items` so the rule is assertable without a plan root on disk or process env. Positive-controlled: both new tests fail against the previous predicate, the first dropping exactly the workspace-scoped plan. Gates: cargo test --workspace --no-fail-fast 8325 passed / 0 failed; clippy --workspace -D warnings clean; fmt clean; unwrap-ratchet OK (389/389). Co-Authored-By: Claude Opus 5 (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.
The work board currently returns zero ExecPlans to any caller whose tenant claim is not
default— silently, as200 {"count":0}.What happened
a413ce6f(#686, the red-steel replay) bound/v1/workto the caller's authenticated tenant so one tenant's kanban rows could not be counted into another's roll-up. That is right for kanban. It was applied to the ExecPlan projection too, which has no tenant dimension at all:list_execplanswalks plan files under one root and stamps every itemtenant_id: None, sowork_tenant_id(w) == tenant_idcompares"default"against the caller's tenant and drops every plan.Why it went unnoticed
200withcount: 0is indistinguishable from an empty backlog or an unconfigured root — and the console's own copy names the unset-CRUX_EXECPLANS_ROOTcase with the same signature, which sends you looking at host env instead of at tenant scoping.Observed on host crux v0.5.62 with an agent token scoped to tenant
work:The same host listed 42 plans at v0.5.59, before this shipped.
attention.rs:73calls the same helper, which is theneeds_you: 0.The change
Filter only the items that actually declare a tenant. Workspace-scoped plans (
tenant_id: None) are visible to any authorized reader — the same argument the code already makes one line above forproject_id— while a future tenant-stamped plan stays narrowed. Kanban binding is untouched.Split the narrowing into
filter_execplan_itemsso the rule is assertable without a plan root on disk or process env.Verification
Two tests, both positive-controlled — they fail against the previous predicate, the first dropping exactly the workspace-scoped plan:
workspace_scoped_plans_are_visible_to_a_non_default_tenanttenant_stamped_plans_are_still_narrowed_to_the_caller(so the predicate is not vacuously true)Gates:
cargo test --workspace --no-fail-fastgreen ·clippy --workspace -D warningsclean · fmt clean · unwrap-ratchet 389/389.🤖 Generated with Claude Code