STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
stardesign.documents Namespace Reference

Classes

class  DesignStore
class  NamePayload
class  CopyPayload
class  SharePayload
class  FeaturedPayload
class  RequesterPayload

Functions

None _write_json_atomic (Path p, object data)
APIRouter make_router (DesignStore store, str prefix, str sub="")

Variables

 request = request
 viewer = store.ud.current_user(request)
tuple main = (main["owner"], main["id"]) if main else None
 star = store.read_star()
bool|None _admin = None

Detailed Description

Per-user designs with sharing, microversion history and named releases.

The three design tools -- EngineDesign, pid-designer, recovery-calculator --
share this router. They differ in only four things, all of them
:class:`DesignStore` fields: the ``<app>`` path segment, the route prefix, the
payload shape (``{"config": ...}`` vs ``{"nodes": ..., "edges": ...}``), and the
noun used in user-facing strings.

Three tiers of durability:

* **autosave** -> a fast working copy on the volume (``current.json``). Never
  hits S3. ``/load`` always returns it -- the freshest state.
* **microversions** -> automatic snapshots pushed to storage over time
  (throttled to the store's ``micro_interval`` while editing) plus a best-effort
  ``/flush`` when the tab closes. The safety net; S3 object versioning + a
  lifecycle rule prunes old ones in prod, files on the volume in dev.
* **releases** -> explicit, immutable, user-named milestones ("0.1").

Version history lives in :mod:`stardesign.storage`. This module owns the working
copy and the per-user document index only. Auth is Caddy's job; a missing header
just means the ``local`` user (never a rejection). See :mod:`stardesign.userdata`.

Sharing
-------
A design lives in its creator's folder, but that is only *where it lives* -- it
is not a privilege level. The creator and everyone in the record's ``sharedWith``
list are equally editors; there is no owner-only operation. Any design can also
be read and copied by anyone, which is what the view-only tree in the UI browses.

Every design-scoped handler resolves through ``_resolve_doc``, which is the one
place cross-user access is granted -- and, since this module is shared, the one
place in the whole fleet. It matters that it returns a :class:`DocRef` rather
than a directory name: the write helpers below happily ``mkdir`` whatever path
they are handed, so a handler that *forgot* ``?owner=`` would not 403 -- it would
silently write a real ``current.json`` into the caller's own folder with no index
entry behind it, and the edit would vanish on reload. ``/flush`` rides a
``sendBeacon`` whose response nobody reads, so that failure would be completely
silent on the most loss-prone path. A ``DocRef`` only exists if an index record
was found, so that cannot happen.

Checkouts
---------
Concurrent editing is not resolved, it is prevented. At most one person holds a
design's write token at a time, and only the holder may save. Taking it is
explicit (opening a design never takes it, so viewing never blocks a colleague);
it lapses on its own after ``lock_ttl`` without a save, and on tab close.
Hiding the tab -- switching away, minimising, closing a lid -- does not release
it; only a real close does.

A lapse frees the design for whoever acts first. If that is the holder -- they
came back and saved or touched the canvas, and nobody took it in the meantime --
the hold is simply theirs again (``DesignStore.claim_for_write``); refusing
them was how edits were lost with nobody else anywhere near the design. If
someone else got there first, the holder's saves are refused, and ``/rescue``
keeps what they carried as a new design of the holder's own.

The compare-and-set runs inside ``_index_lock``, the same ``flock`` that already
serialises index writes, so two simultaneous takes cannot both succeed. That
holds across the several workers each API runs.

**A constraint worth knowing:** ``flock`` is per-machine. All three design tools
run on the one apps machine sharing the ``userdata`` volume, so this is sound
today. Running a design tool on a second machine against the same volume would
break the guarantee silently, and needs a different primitive.

Open to all, and the main design
--------------------------------
Opt-in modes, all off by default so the apps that do not set them behave
exactly as before (pid-designer turns both on):

* ``open_to_all`` -- every design is editable by everyone; checkouts are what
  stop two people saving at once. ``recent_days`` then keeps the editable list
  short: other people's designs not updated within that window move to
  ``/browse``, where they can still be opened. Your own never age out.
* ``featured`` -- an admin may mark one design as the team's main one. Everyone
  can open it, it heads every list and the window never hides it, and **only an
  admin may change it** (unless ``main_admin_only`` is off): everyone else takes
  a copy. Admins come from ``is_admin`` (see :mod:`stardesign.admins`); the
  default is nobody.

The STAR collection
-------------------
``curated`` is the third mode, and the one pid-designer runs (with
``open_to_all`` off and ``main_admin_only`` off):

* Admins choose the **STAR set** (``PUT/DELETE /{id}/star``, kept in the
  app's ``star.json``). Everyone sees those; the main design is always one.
* Everyone else sees their own designs and those shared with them. **Admins see
  everything**, and may edit everything.
* Editing is the creator, the share list, and admins. Only the creator or an
  admin may change the share list -- here it is a real boundary, not
  housekeeping.
* Anyone who can see a design may **ask to edit** it (``POST /{id}/access``);
  the creator or an admin approves (they join the share list) or denies.
  ``GET /requests`` lists what the caller may answer.
* "Can read" narrows to "can see": nobody copies a design they cannot open.

There is deliberately no delete endpoint: a design is editable by more than one
person, so a delete button is one misclick away from destroying a group project
with only a server-admin restore behind it. Cleanup is an admin operation on the
volume.

Function Documentation

◆ _write_json_atomic()

None stardesign.documents._write_json_atomic ( Path p,
object data )
protected

◆ make_router()

APIRouter stardesign.documents.make_router ( DesignStore store,
str prefix,
str sub = "" )
The design CRUD + sharing router for one app.

``prefix`` is the APIRouter prefix (e.g. ``/api/engine/documents``); ``sub``
is an extra path segment beneath it, which only pid-designer uses -- its
router is mounted at ``/api/pid`` and nests these routes under
``/diagrams`` so ``/api/pid/users`` can sit alongside them.

Everything is defined in here as closures over ``store`` rather than as
module-level functions taking it: the alternative is threading one more
argument through twenty helpers, which is exactly the kind of repetition
that lets one of them get forgotten.

Definition at line 445 of file documents.py.

Variable Documentation

◆ _admin

bool | None stardesign.documents._admin = None
protected

Definition at line 606 of file documents.py.

◆ main

tuple stardesign.documents.main ( void ) = (main["owner"], main["id"]) if main else None

Definition at line 604 of file documents.py.

◆ request

stardesign.documents.request = request

Definition at line 601 of file documents.py.

◆ star

stardesign.documents.star = store.read_star()

Definition at line 605 of file documents.py.

◆ viewer

stardesign.documents.viewer = store.ud.current_user(request)

Definition at line 602 of file documents.py.