You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Current TicketTask persistence has user_id and organization_id but no workspace_id. PR #1486 can scope email-linked task joins through Email.workspace_id, but the task aggregate itself cannot enforce workspace ownership. CodeRabbit's exact-head review identified the remaining WebDAV path: a task id owned by the same user/organization can still be addressed from another workspace because webdav_service has no task-level workspace predicate; hiding the joined email is not an ownership invariant.
This is a structural authorization/data-model gap, not a route-specific query tweak. Fix it in the Naruon canonical owner rather than adding another ACL workaround.
Required domain/schema repair
Add non-null workspace_id to TicketTask as part of the aggregate owner scope (user_id, organization_id, workspace_id).
Add an Alembic migration that backfills email-linked tasks from email_records.workspace_id and defines an explicit, deterministic policy for existing source-less tasks; no guessed cross-workspace default.
Move task repositories/queries/mutations (API, WebDAV, Reply-SLA, imports/workers) to the same three-part workspace owner invariant.
Preserve external/legacy compatibility at boundaries; do not duplicate source ownership truth in consumers.
Add cross-workspace negative tests for GET/list/update/WebDAV/Reply-SLA paths and a real PostgreSQL migration acceptance test.
Update DDD/ADR/data-model documentation so TicketTask workspace ownership is authoritative rather than inferred from an optional related email.
Acceptance
A same-user/same-organization session in workspace B cannot discover, read, mutate, sync, or infer the existence/state of a task owned by workspace A, whether the task has a related email or is source-less.
Existing migrated rows receive an auditable workspace assignment or fail migration/provisioning closed; no silent arbitrary assignment.
Repository-owned tests plus real PostgreSQL migration tests are GREEN on the unchanged exact head.
Evidence: PR #1486 review thread PRRT_kwDOSNjZ2s6hzF2d, predecessor exact head 9ebb5953463059eaae3a5053ef4623ef89347203.
Live validation — 2026-09-19
Exact source audit on #148622c173bb93ef15c3244477fe32540aeb2bcf1199 confirms this is still a real aggregate-boundary RED, not a stale review finding:
backend/db/models.py::TicketTask still has user_id and nullable organization_id but no workspace_id.
backend/api/tasks.py::_build_task_query scopes joined Email rows by workspace but explicitly admits TicketTask.related_email_id IS NULL; source-less tasks therefore have no workspace ownership predicate for list/PATCH.
backend/services/webdav_service.py::determine_knowledge_materialization_intent_from_db constrains the optional joined Email by workspace but selects TicketTask itself only by task/user/organization.
backend/services/noema_agent.py::tool_list_tasks and tool_update_task_status select/update tasks by user/organization (plus task uid for update) without workspace, even though the agent dependency already carries workspace_id.
The implementation order remains constrained by migration ownership. #15039151c75568c582c8147cfee6757cd00a9b4d60b7 owns the deterministic workspace owner/organization bindings and canonical 0018_workspace_registry → 0019_email_read_state_repair → 0020_workspace_organization_binding → 0021_workspace_personal_owner_binding chain. #1486 carries a separate descendant migration history. Adding TicketTask.workspace_id directly on the unreconciled #1486 ancestry would either guess a source-less workspace or cement a parallel/colliding migration lineage.
Decision: keep this issue open/high and do not mask it with another route predicate. First reconcile the canonical workspace/migration owner through the normal non-force stack, then implement the aggregate invariant. After reconciliation, email-linked tasks backfill from Email.workspace_id; a source-less task may be assigned only from one auditable canonical workspace-owner binding. Zero or multiple valid candidates must fail migration/provisioning closed with explicit diagnostics; no arbitrary personal/default workspace is allowed.
Required negative acceptance after that reconciliation includes Tasks API source-less list/PATCH, WebDAV task lookup/materialization, Noema list/update, Reply-SLA creation/reload, imports/workers, and a real PostgreSQL migration containing ambiguous and unassignable legacy rows.
Live prerequisite topology
The earlier shorthand .github#2040 repair → external dispatch is no longer sufficient. Protected .github/main remains 64aa08d7fa487deacd41c761c36277ca68cab6c9; scheduler/producer owner #2040 remains exact 12c3fa6f3623aa5f2979d3d5ee4ed987002a6c0d, Draft/open/non-mergeable. Because both sides changed the shared scheduler after merge base fb17ef556f94f673234aa557254ae52779e9a7b0, #2040 must first perform ordinary/non-force path-wise protected-main reconciliation, preserving its valid owner deltas while adopting compatible current-main queue/coalescing/capacity behavior and resolving the production repository-identity RED.
Central base-SAST/security is also a distinct prerequisite. .github#2269@a83d6d325c28620bb393d7b79a3cc74fe8130c38 and .github#2272@5b9e8642361818769d58af6f4e17a6087c90f6ad both harden initial https://api.github.com admission, but default urllib.request redirect handling still has a reproduced credential-egress RED: a cross-origin 30x can carry the Authorization header unless redirects are rejected or every redirect target is revalidated. #2272 additionally carries the deploy-pages.yml caller-input shell-injection repair, but central Semgrep excludes .github/workflows, so executable regression coverage is still required. #2272 has not yet fully inherited #2269's focused CodeQL-helper regression delta; sibling closure/supersession is therefore not justified.
Related #2268/#2271 repository-identity slices are source authority only until their own acceptance settles. The external authenticated repository_dispatch canary remains independent of repository-local CodeQL success.
Current dependency order is therefore: central #2269/#2272 valid production+test delta convergence or a verified complete successor → #2040 path-wise protected-main reconciliation with #2268/#2271 adopt/adapt where applicable → fresh exact-head central security/SAST/CodeQL + qualifying review → unchanged external dispatch canary → #1623 revalidation against the then-current vulnerability database and protected integration → #1694 → #1691 → #1503 protected workspace/migration owner → #1486 ordinary adoption/rechain → this #1673 aggregate implementation and exact-head PostgreSQL/security/review acceptance.
This ordering is an owner/migration repair finding, not permission to close or silently defer the product gap.
Problem
Current
TicketTaskpersistence hasuser_idandorganization_idbut noworkspace_id. PR #1486 can scope email-linked task joins throughEmail.workspace_id, but the task aggregate itself cannot enforce workspace ownership. CodeRabbit's exact-head review identified the remaining WebDAV path: a task id owned by the same user/organization can still be addressed from another workspace becausewebdav_servicehas no task-level workspace predicate; hiding the joined email is not an ownership invariant.This is a structural authorization/data-model gap, not a route-specific query tweak. Fix it in the Naruon canonical owner rather than adding another ACL workaround.
Required domain/schema repair
workspace_idtoTicketTaskas part of the aggregate owner scope (user_id,organization_id,workspace_id).email_records.workspace_idand defines an explicit, deterministic policy for existing source-less tasks; no guessed cross-workspace default.TicketTaskworkspace ownership is authoritative rather than inferred from an optional related email.Acceptance
Evidence: PR #1486 review thread
PRRT_kwDOSNjZ2s6hzF2d, predecessor exact head9ebb5953463059eaae3a5053ef4623ef89347203.Live validation — 2026-09-19
Exact source audit on #1486
22c173bb93ef15c3244477fe32540aeb2bcf1199confirms this is still a real aggregate-boundary RED, not a stale review finding:backend/db/models.py::TicketTaskstill hasuser_idand nullableorganization_idbut noworkspace_id.backend/api/tasks.py::_build_task_queryscopes joinedEmailrows by workspace but explicitly admitsTicketTask.related_email_id IS NULL; source-less tasks therefore have no workspace ownership predicate for list/PATCH.backend/services/webdav_service.py::determine_knowledge_materialization_intent_from_dbconstrains the optional joined Email by workspace but selectsTicketTaskitself only by task/user/organization.backend/services/noema_agent.py::tool_list_tasksandtool_update_task_statusselect/update tasks by user/organization (plus task uid for update) without workspace, even though the agent dependency already carriesworkspace_id.The implementation order remains constrained by migration ownership. #1503
9151c75568c582c8147cfee6757cd00a9b4d60b7owns the deterministic workspace owner/organization bindings and canonical0018_workspace_registry → 0019_email_read_state_repair → 0020_workspace_organization_binding → 0021_workspace_personal_owner_bindingchain. #1486 carries a separate descendant migration history. AddingTicketTask.workspace_iddirectly on the unreconciled #1486 ancestry would either guess a source-less workspace or cement a parallel/colliding migration lineage.Decision: keep this issue open/high and do not mask it with another route predicate. First reconcile the canonical workspace/migration owner through the normal non-force stack, then implement the aggregate invariant. After reconciliation, email-linked tasks backfill from
Email.workspace_id; a source-less task may be assigned only from one auditable canonical workspace-owner binding. Zero or multiple valid candidates must fail migration/provisioning closed with explicit diagnostics; no arbitrary personal/default workspace is allowed.Required negative acceptance after that reconciliation includes Tasks API source-less list/PATCH, WebDAV task lookup/materialization, Noema list/update, Reply-SLA creation/reload, imports/workers, and a real PostgreSQL migration containing ambiguous and unassignable legacy rows.
Live prerequisite topology
The earlier shorthand
.github#2040 repair → external dispatchis no longer sufficient. Protected.github/mainremains64aa08d7fa487deacd41c761c36277ca68cab6c9; scheduler/producer owner #2040 remains exact12c3fa6f3623aa5f2979d3d5ee4ed987002a6c0d, Draft/open/non-mergeable. Because both sides changed the shared scheduler after merge basefb17ef556f94f673234aa557254ae52779e9a7b0, #2040 must first perform ordinary/non-force path-wise protected-main reconciliation, preserving its valid owner deltas while adopting compatible current-main queue/coalescing/capacity behavior and resolving the production repository-identity RED.Central base-SAST/security is also a distinct prerequisite.
.github#2269@a83d6d325c28620bb393d7b79a3cc74fe8130c38and.github#2272@5b9e8642361818769d58af6f4e17a6087c90f6adboth harden initialhttps://api.github.comadmission, but defaulturllib.requestredirect handling still has a reproduced credential-egress RED: a cross-origin 30x can carry theAuthorizationheader unless redirects are rejected or every redirect target is revalidated. #2272 additionally carries thedeploy-pages.ymlcaller-input shell-injection repair, but central Semgrep excludes.github/workflows, so executable regression coverage is still required. #2272 has not yet fully inherited #2269's focused CodeQL-helper regression delta; sibling closure/supersession is therefore not justified.Related #2268/#2271 repository-identity slices are source authority only until their own acceptance settles. The external authenticated
repository_dispatchcanary remains independent of repository-local CodeQL success.Current dependency order is therefore: central #2269/#2272 valid production+test delta convergence or a verified complete successor → #2040 path-wise protected-main reconciliation with #2268/#2271 adopt/adapt where applicable → fresh exact-head central security/SAST/CodeQL + qualifying review → unchanged external dispatch canary → #1623 revalidation against the then-current vulnerability database and protected integration → #1694 → #1691 → #1503 protected workspace/migration owner → #1486 ordinary adoption/rechain → this #1673 aggregate implementation and exact-head PostgreSQL/security/review acceptance.
This ordering is an owner/migration repair finding, not permission to close or silently defer the product gap.