Task: Model sandboxes
Table of Contents
This page documents a task in the Add sandboxes and virtual books story. It captures the goal, current status, acceptance, and any notes or results.
1. Goal
Let a user open a sandbox where the work belongs, with its own portfolio tree that no official portfolio contains.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Add sandboxes and virtual books |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-10-03 |
3. Acceptance
- sandbox and sandbox_member are generated in ores.refdata: purpose, anchor, owner, visibility, status and review date.
- A portfolio belongs to the same sandbox as its parent, or both to none, so no official portfolio contains a sandbox portfolio.
- Opening a sandbox needs the open_sandbox right at an official anchor; a function answers whether an account may see a sandbox.
4. Plan
sandbox: name, purpose (experiment, sample, import), anchor portfolio (fixed), owner account, visibility (private, shared, members), status (open, archived), review date. Its insert trigger refuses an anchor that is itself a sandbox portfolio, and asks foropen_sandboxat the anchor when the sandbox is opened or changes owner; a version that keeps the owner, such as archiving, does not ask again.sandbox_member: a sandbox and an account, for visibilitymembers.portfolio.sandbox_id: optional and fixed. The portfolio trigger keeps a portfolio in its parent's sandbox, or both in none, so a sandbox's portfolios form their own tree and no official portfolio contains one.ores_refdata_account_sees_sandbox_fn: the owner always sees a sandbox; anyone withreadat the anchor sees a shared one; members see a members one. PL/pgSQL, with the caller's rights.- The generated tests open a sandbox the way a user would, through seed
snippets that create a service account and grant it
open_sandboxat the seeded anchor.
5. Measurements
Every number this task claims about a tree, with the command that produced it
and the commit it was measured at. A number without both is not a measurement,
because a clean-up record describes a tree and trunk moves under a branch: a
consumer count, a gate's component count and a suite's size all change with no
code changing, and the reader of a bare number cannot tell which tree it came
from. Where the raw output matters, put it in an evidence/ file beside the
task and name that file in the Command column.
| What | Value | Command | Commit |
|---|---|---|---|
6. Notes
7. Test Scenarios
Manual QA scenarios (scaffolded via compass add test_scenario)
that verify this task. Link
new ones here as they're created; the scenario doc itself links back
via its "Verifies task" field.
| Scenario | State | Notes |
|---|---|---|
8. PRs
| PR | Title |
|---|---|
| #2550 | [refdata] Model sandboxes |
9. Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | Sandbox portfolios leak into official reads | refdata RLS | Fix | A restrictive policy on portfolios shows and accepts a sandbox's portfolios only for an actor who may see the sandbox; a session with no actor sees none. Covers every generated read, the count and list-by-sandbox. |
| 2 | Anyone with write can write into another's sandbox | refdata RLS | Fix | The same policy's check refuses the write. |
| 3 | Member rows can move between sandboxes | sandbox_member | Fix | Both columns are fixed. |
| 4 | Archived sandboxes stay writable | portfolio | Fix | The portfolio trigger refuses a write into an archived sandbox. |
| 5 | Closing a sandbox orphans its portfolios | sandbox | Document | The soft-key convention; the model says so. |
| 6 | Owner change without the new owner's consent | sandbox | Document | The model says consent is not recorded. |
| 7 | Anchor check reveals sandbox portfolios | sandbox | Keep | Ids are UUIDs and the anchor must be readable to be named. |
| 8 | list_portfolios refuses a stated filter | portfolio | Keep | That comes from main's list contract, applied by regeneration; no caller states a filter. |
| 9 | (CI) Shell scripts not tangled | shell scripts | Fix | Tangled; the portfolio right scripts PR 2544 missed come along. |
| 10 | (Found) The refdata service cannot read iam accounts | portfolio_right, sandbox | Fix | Account liveness and the actor's account resolve through narrow security definer helpers, so the invoker functions work under any service role. |
10. Result
sandboxandsandbox_memberare generated inores.refdataand wired by hand like the earlier refdata entities, with tenant row-level security.portfoliogainssandbox_idand a list-by-sandbox read.- In Postgres, in a transaction that rolled back, with the portfolio
trigger live:
- an account holding
open_sandboxon desk A opened a sandbox anchored at A's sub-desk; without the right it was refused (42501); - an official portfolio under a sandbox one, and a sandbox portfolio under an official one, were refused (23514);
- a sandbox anchored at a sandbox portfolio was refused (23514);
- moving a portfolio out of its sandbox was refused (23514, fixed column);
- visibility held as specified: the owner sees a private sandbox and
another account does not; a shared one is seen with
readat the anchor and not without; a members one is seen by a member and not by a non-member.
- an account holding
- The generated sandbox and member tests open a sandbox through seed
snippets that grant
open_sandboxfirst, and pass. - After review, as the refdata service role in a transaction that rolled back: the owner saw the sandbox portfolio, another account and a session with no actor saw none, another account's write was refused (42501, row security), and a write into an archived sandbox was refused (23514).
- Build clean; ctest 89 of 89 on a recreated database.