Files
PacketStation/server/packetstation/policy/custody.py
T
spencermandClaude Fable 5.1 37d9a0cb50 Phase 2: core domain, policy engine, packet API, Clerical screen
Migration 0003: projects, aliases, renumbers, units, packets and every child table,
change orders, assets, documents, ITPs, station limits, department weld flag.
Policy package: Principal, PacketState, Action rules, custody, preconditions,
CO hold, order of checks, can/blocked/holder on reads. docs/POLICY.md with the
tightenings table for Seth. Write pipeline: FOR UPDATE, CAS on status and dept,
If-Match row versions, server-assigned numbers, field CAS, version bump, packet
log, audit, NOTIFY, signatures, IT reasons, filed lock and HMAC seal.
API: /api/packets (header, transitions, work orders, pages, docs, move, remove,
BOM, shorts, welds, heat tags, heat table, stage, checklist, clock, warehouse
stage, Q.C., change orders, log, seal), /api/projects, /api/units, /api/assets,
/_blob. Frontend: Clerical home, projects and units, packet page with pdf.js
and QR codes, hand draft, heat map, send to warehouse, department or Q.C.
Tests: policy matrix, API, concurrency, schema, projects; Playwright Clerical
draft scenario. Verified on docker-1 with the CI stack and the Samba DC.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-10-16 09:31:45 -06:00

124 lines
4.4 KiB
Python

"""Custody: who holds a packet right now, and the Who predicates. A direct port of guard.js holds()."""
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
from .actions import ACTIVE, SUPPORT_CODES, Who
from .principal import PacketState, Principal
WORKER, LEAD, SUPERVISOR = 1, 2, 3
def works_on(principal: Principal, state: PacketState) -> bool:
"""A member of the packet's department whose station limits allow the packet's station.
Leads and supervisors always pass. A worker passes when they have no station limits or the limits hold
the packet's station. This is the old worksOn(); START and SUBMIT add the assigned-welder check.
"""
level = principal.level(state.dept)
if level == 0:
return False
if level >= LEAD:
return True
return not principal.station_limits or (
state.station_id is not None and state.station_id in principal.station_limits
)
def holds(principal: Principal, state: PacketState) -> bool:
"""True when the principal holds the packet in its current status. Admin is not a holder; see the bypass."""
st = state.status
if st == "draft":
return "CL" in principal.support
if st == "warehouse":
return "WH" in principal.support
if st in ("ready", "review"):
return principal.level(state.dept) >= LEAD
if st in ACTIVE:
return principal.level(state.dept) >= LEAD or works_on(principal, state)
if st == "qc":
return "QC" in principal.support
if st == "complete":
return "CL" in principal.support
return False
def holder_name(state: PacketState, dept_name: str | None = None, station_name: str | None = None) -> str:
"""Who has the packet, for messages. The department name falls back to its code."""
st = state.status
if st == "draft":
return "Clerical"
if st == "warehouse":
return "Warehouse"
if st == "qc":
return "Q.C."
if st == "complete":
return "Clerical (data book)"
if st == "filed":
return "the data book (filed)"
name = dept_name or state.dept
if st in ACTIVE and station_name:
return f"{name} · {station_name}"
return name
def _works_on_assigned(principal: Principal, state: PacketState) -> bool:
"""START and SUBMIT: a lead or above in the department, or the assigned welder within their station limits."""
level = principal.level(state.dept)
if level >= LEAD:
return True
return (
level == WORKER
and principal.user_id is not None
and state.welder_id is not None
and str(state.welder_id) == str(principal.user_id)
and works_on(principal, state)
)
def _co_verifier(principal: Principal, extra: Mapping[str, Any]) -> bool:
"""A supervisor of the department named in the request (extra["dept"])."""
dept = extra.get("dept")
if not isinstance(dept, str) or not dept:
return False
if dept in ("WH", "QC"):
return dept in principal.support_sup
if dept in SUPPORT_CODES:
return False # Clerical issues change orders; it never verifies them. PM has no supervisor.
return principal.level(dept) >= SUPERVISOR
def allows(who: Who, principal: Principal, state: PacketState, extra: Mapping[str, Any]) -> bool:
"""Evaluate one Who predicate. Admin is never allowed here except by Who.ADMIN; the bypass is a rule."""
if who is Who.CL:
return "CL" in principal.support
if who is Who.CL_SUP:
return "CL" in principal.support_sup
if who is Who.WH:
return "WH" in principal.support
if who is Who.WH_SUP:
return "WH" in principal.support_sup
if who is Who.QC:
return "QC" in principal.support
if who is Who.QC_SUP:
return "QC" in principal.support_sup
if who is Who.CL_OR_QC:
return "CL" in principal.support or "QC" in principal.support
if who is Who.HOLDER:
return holds(principal, state)
if who is Who.DEPT_LEAD:
return principal.level(state.dept) >= LEAD
if who is Who.DEPT_SUP:
return principal.level(state.dept) >= SUPERVISOR
if who is Who.WORKS_ON:
return _works_on_assigned(principal, state)
if who is Who.CO_VERIFIER:
return _co_verifier(principal, extra)
if who is Who.ADMIN:
return principal.admin
if who is Who.SYSTEM:
return principal.system is not None
return False