ingestion updates
This commit is contained in:
@@ -72,6 +72,22 @@ ROLE_PERMISSIONS: Dict[str, List[str]] = {
|
||||
"view_nutrition_insights",
|
||||
"optimize_profits",
|
||||
],
|
||||
# A third party who may drop spreadsheets into the review inbox and do
|
||||
# NOTHING else. One permission, deliberately.
|
||||
#
|
||||
# This role exists because API keys carry no per-key scoping:
|
||||
# principal_for_api_key() derives permissions entirely from the role, so
|
||||
# "upload-only" can only be expressed as a role. Reusing `user` would have
|
||||
# been less code and would also have handed an outside contributor
|
||||
# add_product, upload_batch_products and upload_store_inventory - real
|
||||
# write access to the catalog - to solve a problem that needed one verb.
|
||||
#
|
||||
# Nothing this role can do starts work: an uploaded file waits in the inbox
|
||||
# until an admin selects it. So the worst an leaked uploader key costs is
|
||||
# bounded disk (INBOX_MAX_PENDING_FILES), never CPU on a one-vCPU host.
|
||||
"uploader": [
|
||||
"upload_catalog",
|
||||
],
|
||||
}
|
||||
|
||||
VALID_ROLES = frozenset(ROLE_PERMISSIONS)
|
||||
|
||||
@@ -169,6 +169,24 @@ BATCH_RETENTION_DAYS = int(os.getenv("BATCH_RETENTION_DAYS", "7"))
|
||||
# which is precisely how a slow start turns into an unrecoverable spiral.
|
||||
BATCH_AUTO_RESUME = _bool("BATCH_AUTO_RESUME", "false")
|
||||
|
||||
# --- Review inbox (a third party drops files; an admin decides) -------------
|
||||
# Files arrive here from POST /api/uploads/catalog and WAIT. Nothing in this
|
||||
# directory is ever executed until an admin selects it, which is the whole
|
||||
# security property of the feature: an uploader credential can consume bounded
|
||||
# disk but can never consume CPU on a one-vCPU host.
|
||||
INBOX_UPLOAD_DIR = _dir("INBOX_UPLOAD_DIR", DATA_DIR / "inbox")
|
||||
|
||||
# Per-file ceilings are the batch ones (10MB / 2000 rows). This bounds the
|
||||
# QUEUE: how many unreviewed files may accumulate before uploads are refused
|
||||
# with 429. Without it an unattended key fills the disk one valid file at a
|
||||
# time, and every one of them looks legitimate.
|
||||
INBOX_MAX_PENDING_FILES = int(os.getenv("INBOX_MAX_PENDING_FILES", "200"))
|
||||
|
||||
# Consumed and dismissed files are deleted this many days after upload.
|
||||
# Pending files are never purged - deleting something nobody has looked at yet
|
||||
# would lose a colleague's work silently.
|
||||
INBOX_RETENTION_DAYS = int(os.getenv("INBOX_RETENTION_DAYS", "7"))
|
||||
|
||||
# Pristine copies of the bundled seed catalogs and pre-trained models, placed
|
||||
# here by the Dockerfile at a path that is never itself mounted over.
|
||||
#
|
||||
@@ -457,9 +475,15 @@ def _parse_api_keys(raw: str) -> dict:
|
||||
f"comma-separated between entries."
|
||||
)
|
||||
name, role, secret = (p.strip() for p in parts)
|
||||
if role not in {"admin", "user"}:
|
||||
# MUST stay in step with ROLE_PERMISSIONS in app/infrastructure/security.py,
|
||||
# which is the source of truth. It is duplicated rather than imported
|
||||
# because security.py imports THIS module, so importing it back here
|
||||
# would be a cycle. A role added there but not here is rejected at boot
|
||||
# with the message below - loud, and before any request is served.
|
||||
if role not in {"admin", "user", "uploader"}:
|
||||
raise RuntimeError(
|
||||
f"API_KEYS entry {name!r} has role {role!r}; expected 'admin' or 'user'."
|
||||
f"API_KEYS entry {name!r} has role {role!r}; expected 'admin', 'user' "
|
||||
f"or 'uploader'."
|
||||
)
|
||||
if not secret:
|
||||
raise RuntimeError(f"API_KEYS entry {name!r} has an empty secret.")
|
||||
@@ -476,6 +500,13 @@ def _parse_api_keys(raw: str) -> dict:
|
||||
|
||||
# Machine consumers of api.<domain>. Empty by default - browser sessions go
|
||||
# through /api/auth/login instead, and a key that nobody needs is only risk.
|
||||
#
|
||||
# NAME THE KEY FOR ITS FUNCTION, NOT THE PERSON HOLDING IT.
|
||||
# /api/health is public and reports {name, role, fingerprint} for every
|
||||
# configured key (describe_api_keys in security.py). The secret is never
|
||||
# exposed, but the NAME is - so `catalog-drop:uploader:...` is right and
|
||||
# `priya-laptop:uploader:...` publishes a colleague's name to anyone who
|
||||
# curls the health endpoint.
|
||||
API_KEYS = _parse_api_keys(os.getenv("API_KEYS", ""))
|
||||
|
||||
# Default RAG behaviour
|
||||
|
||||
Reference in New Issue
Block a user