Repository navigation
Feat/project soft tenant scope - #22
Merged
Merged
Conversation
Every pipeline stamps the organisation and project that own a source device onto the resources it derives from that device's media, and Hub API later reads them back with a single selector. Monitor had the only implementation; the remaining pipelines were about to copy it. Copying is the wrong shape for this. Two services that resolve the same device differently do not fail loudly: they write resources that are silently invisible to the tenant, or visible to the wrong one. Independently deployed services can only agree if there is exactly one implementation, so it moves here before the duplication spreads. The two halves of the resolution are deliberately asymmetric. The organisation is verified against persisted records, because it is the tenant boundary, a wrong value crosses tenants, and a populated collection exists to check against; ambiguous, orphaned, and conflicting ownership all fail closed rather than guess. The project is assigned from models/pkg/projectscope, because during the hidden rollout no authoritative project record exists and any query would let two services disagree. An empty organisation collection means organisations-bootstrap has not run on that instance rather than that the device is unowned, so a primary organisation is derived from the master user's id — the value the migration will later persist. Erroring there would drop every message on an un-migrated deployment. This lands beside pkg/database rather than inside it: resolution needs models.Device, and pkg/database stays a dependency-light leaf with no models import. Collection names are configuration because services do not all spell them the same way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Before bootstrap has run the organisation collection is empty, so ownership is derived rather than read: a primary organisation reuses the master user's id. That derivation is only meaningful because bootstrap mints one organisation per master user. When ownership followed a sub-user's user_id, the existence check proved only that the sub-user was real. A dangling master link therefore produced an organisation id for an account that no longer exists, and bootstrap will never create it. The media would be stamped with a tenant no reader selects and disappear — the silent failure this resolver exists to prevent, arrived at by a different route. Confirm the owner has a user record before deriving from it. The lookup runs only when a master link was followed and only on the pre-bootstrap path, so the common case pays nothing: when the owner is the queried user itself its existence is already proven, and when an organisation record exists it is returned before this point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
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.
Description
Introduces a shared tenancy resolution layer that consistently determines the organisation and project ownership of devices across services.
The main motivation is to eliminate inconsistent tenant scoping between independently deployed pipelines. Previously, ownership resolution logic could diverge across services, causing resources to become invisible to the correct tenant or incorrectly visible to another tenant without producing explicit failures. By centralizing this logic into a single implementation, tenant scoping becomes deterministic and reliable across the platform.
The new resolver enforces strict organisation ownership validation against persisted records while introducing a soft project-scoping strategy that safely supports the current single-project rollout. Project assignment is derived deterministically instead of relying on potentially incomplete or inconsistent project records, ensuring all services resolve ownership identically during migration phases.
This change also improves backward compatibility by handling legacy ownership formats and pre-bootstrap deployments without breaking existing data flows. Ambiguous or invalid ownership states now fail closed instead of silently producing incorrectly scoped resources, reducing the risk of cross-tenant leakage and difficult-to-debug visibility issues.
Comprehensive tests were added to validate canonical ownership resolution, legacy migration behavior, bootstrap edge cases, and failure scenarios. Dependency updates pull in the required shared models and project scope logic needed to support the new tenancy behavior.