Phase 3: VISUAL feed and M-Files drawings in the worker
ci / frontend (push) Successful in 30s
ci / server (push) Successful in 4m12s
ci / image (push) Successful in 46s
ci / e2e (push) Successful in 1m24s

Migration 0004 (VISUAL and drawing caches, go-live ignored list, resource
map, job tables, order-derived columns with provenance; work order key
includes the operation sequence). The real worker on APScheduler with
settings re-read each fire, pause, run now, advisory locks, job_runs and a
heartbeat. The visual-feed job (five VisualKit reads, COPY and MERGE cache,
suspect-batch guard, the apply in one transaction: drafts, change orders,
retire-moved, work order gone, closed and cancelled observed, projects and
units from order links, auto-release) and the fingerprint. The drawing-lookup
job (queue, budget, download on version change, sha256 into assets, decision
table, staging for rev approval, drawing-origin change orders). Clerical From
VISUAL panel, flags, rev approval, hand uploads, change-order review; Admin
Feed settings and resource map. Synthetic fixture generator and a
fixture-driven worker in CI with a Playwright scenario. Docs updated.

Verified: 3249 server tests and 13 Playwright scenarios on the docker-1 CI
stack, 76 frontend tests, doctor at 0004; one feed and one drawing run
against VISUAL DEV and the vault from the developer machine (counts in the
changelog).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-10-16 14:45:38 -06:00
co-authored by Claude Fable 5.1
parent 16950055e3
commit 591fe90076
103 changed files with 16063 additions and 151 deletions
+99 -1
View File
@@ -247,7 +247,7 @@ def test_v_open_cos(pg_settings: Settings) -> None:
conn.execute("INSERT INTO co_required_depts (co_id, dept_code) VALUES (%s, 'WH'), (%s, '03')", (co, co))
conn.execute(verify, (co, "WH", ids["user"], ids["sig"]))
held = new_co(conn, pk, 2, "visual", "pending", hold=True)
new_co(conn, pk, 3, "drawing", "pending", hold=False)
new_co(conn, pk, 3, "manual", "pending", hold=False) # 0004: one pending feed CO, visual or drawing
rejected = new_co(conn, pk, 4, "manual", "rejected", hold=True)
conn.execute("INSERT INTO co_required_depts (co_id, dept_code) VALUES (%s, '03')", (rejected,))
rows = conn.execute(
@@ -276,6 +276,8 @@ def test_one_pending_visual_co_per_packet(pg_settings: Settings) -> None:
new_co(conn, ids["packet"], 3, "visual", "approved")
with pytest.raises(psycopg.errors.UniqueViolation):
new_co(conn, ids["packet"], 4, "visual", "pending")
with pytest.raises(psycopg.errors.UniqueViolation):
new_co(conn, ids["packet"], 4, "drawing", "pending") # 0004: drawing-origin counts too
with pytest.raises(psycopg.errors.UniqueViolation):
new_co(conn, ids["packet"], 2, "manual", "approved")
@@ -367,3 +369,99 @@ def test_app_can_write_every_table_and_read_every_view(pg_settings: Settings) ->
for priv in ("SELECT", "INSERT", "UPDATE", "DELETE"):
allowed = one(conn, "SELECT has_table_privilege('ps_app', %s, %s)", (view, priv))
assert allowed is (priv == "SELECT"), (view, priv)
FEED_TABLES = [
"visual_batches",
"visual_rows",
"visual_materials",
"visual_orders",
"visual_order_links",
"visual_labor",
"visual_row_changes",
"visual_material_changes",
"visual_ignored_wos",
"visual_resource_map",
"drawing_lookups",
"job_requests",
"job_runs",
"job_schedule",
"worker_heartbeat",
]
def test_feed_tables_and_app_grants(pg_settings: Settings) -> None:
with db.connect_as("migrate", pg_settings, autocommit=True) as conn:
names = [r[0] for r in conn.execute("SELECT name FROM schema_migrations ORDER BY name").fetchall()]
assert "0004_feed.sql" in names
with app_conn(pg_settings) as conn:
for table in FEED_TABLES:
for priv in ("SELECT", "INSERT", "UPDATE", "DELETE"):
assert one(conn, "SELECT has_table_privilege('ps_app', %s, %s)", (table, priv)) is True, (table, priv)
jobs = [r[0] for r in conn.execute("SELECT job FROM job_schedule ORDER BY job").fetchall()]
assert jobs == ["drawing-lookup", "housekeeping", "visual-feed"]
assert one(conn, "SELECT target FROM visual_resource_map WHERE resource = 'WELD-PART'") == "03"
assert one(conn, "SELECT target FROM visual_resource_map WHERE resource = 'Electical'") == "14"
assert one(conn, "SELECT count(*) FROM visual_resource_map WHERE confirmed") == 0
with pytest.raises(psycopg.errors.CheckViolation):
conn.execute("INSERT INTO visual_resource_map (resource, target) VALUES ('X', 'nowhere')")
def test_feed_columns_are_text_ids(pg_settings: Settings) -> None:
with app_conn(pg_settings) as conn:
cols = dict(
conn.execute(
"SELECT table_name || '.' || column_name, data_type FROM information_schema.columns"
" WHERE table_schema = 'public' AND column_name IN ('wo_base', 'lot', 'split', 'sub', 'seq', 'piece',"
" 'part', 'cust_order_id', 'dwg')"
).fetchall()
)
for name, kind in cols.items():
col = name.split(".")[1]
if col in ("seq", "piece"):
assert kind == "integer", name
else:
assert kind == "text", name
for t, c in (
("packets", "visual_cancelled_at"),
("packets", "wo_gone_at"),
("packet_work_orders", "rev"),
("projects", "order_ref"),
("units", "customer_visual"),
("units", "order_pin"),
):
assert (
one(
conn,
"SELECT count(*) FROM information_schema.columns WHERE table_name = %s AND column_name = %s",
(t, c),
)
== 1
), (t, c)
def test_two_operations_of_one_work_order_on_one_packet(pg_settings: Settings) -> None:
"""Amendment 1: the key includes seq; hand rows (seq NULL) stay one per key."""
with app_conn(pg_settings) as conn:
ids = seed(conn)
for seq in (10, 20):
conn.execute(
"INSERT INTO packet_work_orders (packet_id, wo_base, lot, split, sub, seq) "
"VALUES (%s, 'L900-02', '1', '0', '0', %s)",
(ids["packet"], seq),
)
with pytest.raises(psycopg.errors.UniqueViolation):
conn.execute(
"INSERT INTO packet_work_orders (packet_id, wo_base, lot, split, sub, seq) "
"VALUES (%s, 'L900-02', '1', '0', '0', 20)",
(ids["packet"],),
)
conn.execute(
"INSERT INTO packet_work_orders (packet_id, wo_base, lot, split, sub) VALUES (%s, 'H1', '1', '0', '0')",
(ids["packet"],),
)
with pytest.raises(psycopg.errors.UniqueViolation):
conn.execute(
"INSERT INTO packet_work_orders (packet_id, wo_base, lot, split, sub) VALUES (%s, 'H1', '1', '0', '0')",
(ids["packet"],),
)