Archive copy must succeed before a packet counts as filed (Gitea issue 57)
FILE checks the archive folder and copies the packet inside the filing transaction. A failed check or copy rolls the filing back, so the packet stays in its previous stage; the person filing gets 503 archive_failed, and in its own transaction the server writes an archive.filing_blocked audit row and raises the critical archive_failed condition to Admin for that packet (who tried, folder, error). The next successful filing clears it. The archive-copy job now only catches up filings made while the archive was off. PLAN question 53, CHANGELOG, FEED and SECURITY updated. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -18,9 +18,9 @@ from psycopg.types.json import Jsonb
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from .. import audit, events, policy
|
||||
from ..domain import archive, packets, search, shop
|
||||
from ..domain import assets as asset_store
|
||||
from ..domain import ncr as ncrs
|
||||
from ..domain import packets, search, shop
|
||||
from ..domain import qc as qc_dom
|
||||
from ..domain.pipeline import (
|
||||
Expect,
|
||||
@@ -543,6 +543,10 @@ TRANSITION_FIELDS: dict[policy.Action, tuple[str, ...]] = {
|
||||
A.FILE: ("note",),
|
||||
A.ADMIN_REOPEN: ("reason",),
|
||||
}
|
||||
MSG_ARCHIVE = (
|
||||
"The packet could not be copied to the archive: {reason}. Nothing was filed, and Admin has been told."
|
||||
" File it again once the archive works."
|
||||
)
|
||||
DEPT_ACTIONS = (A.RELEASE_TO_SHOP, A.ROUTE_FROM_REVIEW, A.QC_ACCEPT_AND_ROUTE)
|
||||
|
||||
|
||||
@@ -591,29 +595,34 @@ def transition(
|
||||
else:
|
||||
del extra["stamp"]
|
||||
content = {"action": str(action), "packet_id": packet_id, "expect": body.expect.model_dump(), **extra}
|
||||
with packet_write(
|
||||
d,
|
||||
p,
|
||||
packet_id,
|
||||
action,
|
||||
request=request,
|
||||
extra=extra,
|
||||
it_reason=body.it_reason,
|
||||
sign=body.sign,
|
||||
expect=body.expect,
|
||||
sign_content=content,
|
||||
policy_principal=pp,
|
||||
) as w:
|
||||
if action is A.ASSIGN:
|
||||
ok = conn.execute(
|
||||
"SELECT 1 FROM stations WHERE id = %s AND dept_code = %s AND active",
|
||||
(extra["station_id"], w.state.dept),
|
||||
).fetchone()
|
||||
if ok is None:
|
||||
raise ApiError(422, "invalid_station", "Choose a station in this department.")
|
||||
active_user(conn, extra["welder_id"], "welder")
|
||||
shop.welder_check(conn, w.state.dept, str(extra["welder_id"]), int(extra["station_id"]))
|
||||
apply_transition(w)
|
||||
try:
|
||||
with packet_write(
|
||||
d,
|
||||
p,
|
||||
packet_id,
|
||||
action,
|
||||
request=request,
|
||||
extra=extra,
|
||||
it_reason=body.it_reason,
|
||||
sign=body.sign,
|
||||
expect=body.expect,
|
||||
sign_content=content,
|
||||
policy_principal=pp,
|
||||
) as w:
|
||||
if action is A.ASSIGN:
|
||||
ok = conn.execute(
|
||||
"SELECT 1 FROM stations WHERE id = %s AND dept_code = %s AND active",
|
||||
(extra["station_id"], w.state.dept),
|
||||
).fetchone()
|
||||
if ok is None:
|
||||
raise ApiError(422, "invalid_station", "Choose a station in this department.")
|
||||
active_user(conn, extra["welder_id"], "welder")
|
||||
shop.welder_check(conn, w.state.dept, str(extra["welder_id"]), int(extra["station_id"]))
|
||||
apply_transition(w)
|
||||
except archive.FilingCopyFailed as e:
|
||||
# The filing rolled back (Gitea issue 57); record it and tell Admin in a transaction of its own.
|
||||
archive.record_filing_failure(conn, p, packet_id, e)
|
||||
raise ApiError(503, "archive_failed", MSG_ARCHIVE.format(reason=e.message.rstrip("."))) from None
|
||||
return _read(d, pp, packet_id)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user