MEGAINFRA-WEBAPP/scripts/pb-relax-dn-zero-fields.py
DEls 252dd7eb6a fix(dn): let staged ingestion run against the real PocketBase schema
Three faults stopped the staged pipeline from ingesting anything once it
met a live PocketBase rather than a test double.

PocketBase treats 0 as blank for a required number field, and two staged
fields legitimately hold zero: dn_batch_sources.ordinal is 0 for the
first source in a batch, and dn_batch_pages.region is 0 for a whole-page
read. Both creates were rejected with validation_required, so the first
file of every upload failed and no page ever persisted. The migration now
declares them optional, and pb-relax-dn-zero-fields.py relaxes instances
that already have them required.

The staged runner verifies its batch lease and commits the protected
write in one transaction, which needs PocketBase's batch API. That is off
by default, so the runner failed closed with
dn_pipeline_transactions_required and no deployment note explained why.
pb-enable-batch-api.py turns it on and raises the 3s transaction timeout,
which is tight for a commit carrying page images.

compare-dn-pipelines used top-level await while resolving as CJS, so the
command in the rollout runbook always failed to transform. Renamed .mts.

The tests missed all of this because they mock PocketBase, and the mock
accepts what the real server rejects.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 23:49:58 +02:00

78 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""Relax the staged-ingestion number fields that legitimately hold zero.
PocketBase treats 0 as blank for a ``required`` number field, so
``dn_batch_sources.ordinal`` (0 for the first source in a batch) and
``dn_batch_pages.region`` (0 for a whole-page read) were rejected on every
create. This flips those two fields to optional and touches nothing else.
Dry run unless ``--apply`` is supplied, and ``--apply`` additionally requires
``--confirm-host`` matching the configured PocketBase host exactly.
"""
import sys
from pb_env import PB, http, superuser_token
TARGETS = (("dn_batch_sources", "ordinal"), ("dn_batch_pages", "region"))
APPLY = "--apply" in sys.argv
def confirmation_host():
for index, argument in enumerate(sys.argv):
if argument.startswith("--confirm-host="):
return argument.split("=", 1)[1]
if argument == "--confirm-host" and index + 1 < len(sys.argv):
return sys.argv[index + 1]
return None
def main():
if APPLY and confirmation_host() != PB:
sys.exit(
f"REFUSING --apply: pass --confirm-host with the exact configured "
f"host ({PB})."
)
token = superuser_token()
print(f"auth ok on {PB}")
print(f"target: {PB} mode: {'APPLY' if APPLY else 'DRY RUN'}")
changed = 0
for collection_name, field_name in TARGETS:
code, collection = http("GET", f"/api/collections/{collection_name}", token=token)
if code != 200:
sys.exit(f"collection {collection_name} not found ({code})")
fields = collection.get("fields") or collection.get("schema") or []
target = next((f for f in fields if f.get("name") == field_name), None)
if target is None:
sys.exit(f"{collection_name}.{field_name} not found")
if not target.get("required"):
print(f" {collection_name}.{field_name}: already optional")
continue
changed += 1
if not APPLY:
print(f" {collection_name}.{field_name}: would set required false")
continue
target["required"] = False
code, response = http(
"PATCH",
f"/api/collections/{collection['id']}",
{"fields": fields},
token=token,
)
if code != 200:
sys.exit(f" {collection_name}.{field_name}: PATCH failed {code}: {response}")
print(f" {collection_name}.{field_name}: required -> false")
if not APPLY:
print(f"\nDRY RUN. {changed} field(s) would change. No schema changes written.")
else:
print(f"\nAPPLIED. {changed} field(s) changed.")
main()