Proposed (STA-841). It extends
0327 decision 3: a node’s
path changes only through this move. OS tenancy gate A, gap G1.
Context
ScopeNodeService only added nodes. When an organisation changes hands or a tenant leaves a reseller, the tenant row
was re-parented but its scope node and every scope_path row below it kept the old path, so the new organisation’s
grants (an ancestor row-level-security match on app.scope_paths) never reached them. The rows live in many
services’ databases, so no one statement can move them all.
Decision
ScopeNodeService.move(scope, parent)keeps every id and label and rewrites the path prefix of the node and its descendants inscope.scope_nodesin one statement, in the caller’s transaction. A parent that is the node or below it isscope_move_invalid(ScopeMoveInvalidError, Validation); a level the parent does not take isscope_level_invalid; a move to the current parent changes nothing.ScopeMoved(scope.node.moved.v1, an integration event carryingscopeId,fromandto) is recorded on the moved node and leaves through the outbox with the move’s commit.- Each service re-paths its own rows from its listener with
CrudService.repath(from, to)(@stackra/database):set scope_path = to || subpath(scope_path, nlevel(from)) where scope_path <@ from, both paths parameters, one statement per table, inside the listener’s transaction. Re-pathing is idempotent: a redelivered event finds no row underfrom. - Both the move and each listener run in a scope that reaches both paths (a grant at a common ancestor), so row-level security lets the rows out of the old subtree and into the new one; there is no bypass.
Consequences
- Between the node move and a service’s listener, that service’s rows still carry the old path: a reader in the new organisation does not see them yet (fail closed, never wider).
- A service with a
scope_pathtable must add the listener; the README shows the four lines.
Enforcement
planned —packages/security/scope/src/node/services/__tests__/scope-node.service.test.ts and
packages/data/database/src/core/bases/__tests__/crud-service.base.test.ts.