fsanyoto commited on
Commit
9bf53a1
Β·
verified Β·
1 Parent(s): b4bbc49

Deploy AIOS web (React glide grid + FastAPI slice)

Browse files
Files changed (5) hide show
  1. RELEASES.json +7 -1
  2. VERSION +1 -1
  3. api/automation_engine.py +21 -0
  4. api/main.py +78 -9
  5. api/routes_automation.py +6 -0
RELEASES.json CHANGED
@@ -1,6 +1,12 @@
1
  {
2
- "current": "v32 (b47b27c)",
3
  "releases": [
 
 
 
 
 
 
4
  {
5
  "version": "v32",
6
  "sha": "b47b27c",
 
1
  {
2
+ "current": "v33 (2b115e5)",
3
  "releases": [
4
+ {
5
+ "version": "v33",
6
+ "sha": "2b115e5",
7
+ "date": "2026-08-21",
8
+ "subject": "W39: stop nonproduction Postgres background egress"
9
+ },
10
  {
11
  "version": "v32",
12
  "sha": "b47b27c",
VERSION CHANGED
@@ -1 +1 @@
1
- v32 (b47b27c)
 
1
+ v33 (2b115e5)
api/automation_engine.py CHANGED
@@ -14551,6 +14551,23 @@ def tick(rt, tenant, now=None, log=print):
14551
  return started
14552
 
14553
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14554
  def tick_all(now=None, log=print):
14555
  """Every registered tenant. Fail-quiet per tenant: one tenant's broken store must not stop
14556
  the others' schedules."""
@@ -14558,6 +14575,10 @@ def tick_all(now=None, log=print):
14558
  out = {}
14559
  for slug in _rt.known_tenants():
14560
  try:
 
 
 
 
14561
  out[slug] = tick(_rt.get_runtime(slug), slug, now=now, log=log)
14562
  except Exception as e: # noqa: BLE001
14563
  log(f"[aios-auto] tick {slug} skipped: {type(e).__name__}: {e}")
 
14551
  return started
14552
 
14553
 
14554
+ def _pg_tick_refusal(tenant_slug, backend=None, write_refusal=None):
14555
+ """The Pg write wall, before a background tick reads a tenant's whole workspace.
14556
+
14557
+ A non-production Pg Space may display tenant #0, but its recurring automation work has no
14558
+ durable destination there. The tick would otherwise fetch ``user_tables`` several times per
14559
+ minute merely to discover that its first update is refused. This applies to both scheduler
14560
+ and authenticated external-tick callers; HF remains untouched and a granted QA tenant passes.
14561
+ """
14562
+ actual_backend = backend if backend is not None else os.environ.get("STORE_BACKEND", "hf")
14563
+ if str(actual_backend).strip().lower() != "pg":
14564
+ return None
14565
+ if write_refusal is None:
14566
+ import core.store_pg as _pg
14567
+ write_refusal = _pg.write_refusal
14568
+ return write_refusal(str(tenant_slug))
14569
+
14570
+
14571
  def tick_all(now=None, log=print):
14572
  """Every registered tenant. Fail-quiet per tenant: one tenant's broken store must not stop
14573
  the others' schedules."""
 
14575
  out = {}
14576
  for slug in _rt.known_tenants():
14577
  try:
14578
+ refusal = _pg_tick_refusal(slug)
14579
+ if refusal:
14580
+ log(f"[aios-auto] tick {slug} skipped before runtime read: {refusal}")
14581
+ continue
14582
  out[slug] = tick(_rt.get_runtime(slug), slug, now=now, log=log)
14583
  except Exception as e: # noqa: BLE001
14584
  log(f"[aios-auto] tick {slug} skipped: {type(e).__name__}: {e}")
api/main.py CHANGED
@@ -603,6 +603,11 @@ def _migrate_env_odoo(why):
603
  three different operator actions and a blank Keychain page cannot tell them apart.
604
  """
605
  try:
 
 
 
 
 
606
  import routes_keychain as _kc_routes
607
  from harness import runtime as _runtime
608
  rep = _kc_routes.migrate_env_odoo(_runtime.get_runtime("royal-imports"))
@@ -690,6 +695,49 @@ def _rebuild_meta_relational(why, rt, previous_fingerprint=None, force_relations
690
  "changed": True, "applied": False, "written": {}}
691
 
692
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
693
  def _sweep_automation_schemas(why):
694
  """β›”β›” WAVE 32 Β· `W32-T07` β€” MAKE D'S DECLARATIONS REACH TENANTS THAT ALREADY HAVE THE TABLES.
695
 
@@ -744,6 +792,11 @@ def _sweep_automation_schemas(why):
744
  return
745
  for slug in tenants:
746
  try:
 
 
 
 
 
747
  rt = _runtime.get_runtime(slug)
748
  # β›”β›” FIXED 2026-08-13 β€” THIS SWEEP WAS SPAWNING EIGHT DATABASES IN EVERY TENANT.
749
  # Owner: *"Database for Royal Imports, why we have fucking IG and TIktok databases."*
@@ -831,6 +884,11 @@ def _rebuild_odoo_relational(why, previous_meta_fingerprint=None, meta_state_out
831
  from harness import runtime as _runtime
832
  if not _rel.is_royal("royal-imports"):
833
  return False
 
 
 
 
 
834
  _rt = _runtime.get_runtime("royal-imports")
835
  counts = _rel.refresh(_rt, "royal-imports")
836
  # ⭐⭐ AND THEN COMPUTE THE CELLS, which `refresh` does NOT do.
@@ -979,15 +1037,21 @@ def _store_resync_loop():
979
  _every = 1800
980
  _manual = False
981
  try:
982
- import odoo_relational as _rel_cad
983
- from harness import runtime as _rt_cad
984
- # ⚠ D-245, KNOWN AND NOT FIXED HERE: the slug is hardcoded, so this reads TENANT #0's
985
- # cadence and applies it to a loop that syncs the shared mirror. Correct while one
986
- # tenant has Odoo; wrong the moment a second one does. Out of this ticket's scope and
987
- # named rather than silently inherited.
988
- _secs = _rel_cad.sync_seconds(_rt_cad.get_runtime("royal-imports"))
989
- _manual = _secs is None
990
- _every = 1800 if _secs is None else max(int(_secs), 60)
 
 
 
 
 
 
991
  except Exception: # noqa: BLE001
992
  pass
993
  _t.sleep(_every)
@@ -1091,6 +1155,11 @@ def _prewarm():
1091
  import threading as _th
1092
  _th.Thread(target=_seed_and_sync_store, daemon=True, name="store-seed-sync").start()
1093
  try:
 
 
 
 
 
1094
  from harness import runtime as _runtime
1095
  rt = _runtime.get_runtime("royal-imports")
1096
  routes_customers.warm_default(rt)
 
603
  three different operator actions and a blank Keychain page cannot tell them apart.
604
  """
605
  try:
606
+ refusal = _pg_write_refusal("royal-imports")
607
+ if refusal:
608
+ print(f"[aios-api] odoo keychain migration skipped ({why}) before user_tables read: "
609
+ f"this deployment cannot persist royal-imports ({refusal})")
610
+ return
611
  import routes_keychain as _kc_routes
612
  from harness import runtime as _runtime
613
  rep = _kc_routes.migrate_env_odoo(_runtime.get_runtime("royal-imports"))
 
695
  "changed": True, "applied": False, "written": {}}
696
 
697
 
698
+ def _pg_write_refusal(tenant_slug, backend=None, write_refusal=None):
699
+ """The existing Pg write wall, available *before* a costly runtime read.
700
+
701
+ A non-production Pg Space may read tenant #0 so it can display the real workspace, but it
702
+ may not persist to it. A relational refresh is a guaranteed write: letting it first call
703
+ ``rt.get('user_tables')`` transfers the entire document merely to discover the refusal at
704
+ ``rt.update``. This is not a new authorisation rule. It queries the shipping Pg rule before
705
+ the read, and is intentionally a no-op for the HF backend.
706
+
707
+ ``backend`` and ``write_refusal`` are narrow injection seams for the no-network W39 witness;
708
+ normal callers use the exact deployment environment and ``core.store_pg.write_refusal``.
709
+ """
710
+ actual_backend = (backend if backend is not None
711
+ else os.environ.get("STORE_BACKEND", "hf"))
712
+ if str(actual_backend).strip().lower() != "pg":
713
+ return None
714
+ if write_refusal is None:
715
+ import core.store_pg as _pg
716
+ write_refusal = _pg.write_refusal
717
+ return write_refusal(str(tenant_slug))
718
+
719
+
720
+ _NONPROD_PG_SYNC_EVERY_ENV = "AIOS_NONPROD_PG_SYNC_EVERY"
721
+
722
+
723
+ def _nonprod_pg_sync_seconds(preset=None, backend=None, write_refusal=None):
724
+ """A deployment-only idle cadence for a Pg Space that cannot write tenant #0.
725
+
726
+ Staging deliberately reads the real tenant, but its write wall means it must never persist
727
+ tenant #0's connector setting. Reading that setting every half hour costs a full
728
+ ``user_tables`` transfer. The opt-in secret therefore accepts exactly ``daily`` and asks the
729
+ same write wall *before* building a runtime. Production, HF, an unset secret and any typo
730
+ all retain the shipping tenant-configured cadence.
731
+ """
732
+ raw = preset if preset is not None else os.environ.get(_NONPROD_PG_SYNC_EVERY_ENV, "")
733
+ if str(raw).strip().lower() != "daily":
734
+ return None
735
+ if not _pg_write_refusal("royal-imports", backend=backend,
736
+ write_refusal=write_refusal):
737
+ return None
738
+ return 86400
739
+
740
+
741
  def _sweep_automation_schemas(why):
742
  """β›”β›” WAVE 32 Β· `W32-T07` β€” MAKE D'S DECLARATIONS REACH TENANTS THAT ALREADY HAVE THE TABLES.
743
 
 
792
  return
793
  for slug in tenants:
794
  try:
795
+ refusal = _pg_write_refusal(slug)
796
+ if refusal:
797
+ print(f"[aios-api] schema sweep ({why}) {slug}: skipped before user_tables read "
798
+ f"because this deployment cannot persist that tenant ({refusal})")
799
+ continue
800
  rt = _runtime.get_runtime(slug)
801
  # β›”β›” FIXED 2026-08-13 β€” THIS SWEEP WAS SPAWNING EIGHT DATABASES IN EVERY TENANT.
802
  # Owner: *"Database for Royal Imports, why we have fucking IG and TIktok databases."*
 
884
  from harness import runtime as _runtime
885
  if not _rel.is_royal("royal-imports"):
886
  return False
887
+ refusal = _pg_write_refusal("royal-imports")
888
+ if refusal:
889
+ print(f"[aios-api] odoo relational rebuild skipped ({why}) before user_tables read: "
890
+ f"this deployment cannot persist royal-imports ({refusal})")
891
+ return False
892
  _rt = _runtime.get_runtime("royal-imports")
893
  counts = _rel.refresh(_rt, "royal-imports")
894
  # ⭐⭐ AND THEN COMPUTE THE CELLS, which `refresh` does NOT do.
 
1037
  _every = 1800
1038
  _manual = False
1039
  try:
1040
+ _stage_every = _nonprod_pg_sync_seconds()
1041
+ if _stage_every is not None:
1042
+ _every = _stage_every
1043
+ print("[aios-api] resync cadence uses deployment-only daily override for "
1044
+ "non-production Postgres")
1045
+ else:
1046
+ import odoo_relational as _rel_cad
1047
+ from harness import runtime as _rt_cad
1048
+ # ⚠ D-245, KNOWN AND NOT FIXED HERE: the slug is hardcoded, so this reads TENANT #0's
1049
+ # cadence and applies it to a loop that syncs the shared mirror. Correct while one
1050
+ # tenant has Odoo; wrong the moment a second one does. Out of this ticket's scope and
1051
+ # named rather than silently inherited.
1052
+ _secs = _rel_cad.sync_seconds(_rt_cad.get_runtime("royal-imports"))
1053
+ _manual = _secs is None
1054
+ _every = 1800 if _secs is None else max(int(_secs), 60)
1055
  except Exception: # noqa: BLE001
1056
  pass
1057
  _t.sleep(_every)
 
1155
  import threading as _th
1156
  _th.Thread(target=_seed_and_sync_store, daemon=True, name="store-seed-sync").start()
1157
  try:
1158
+ refusal = _pg_write_refusal("royal-imports")
1159
+ if refusal:
1160
+ print("[aios-api] eager user_tables warm skipped before runtime read: this "
1161
+ f"deployment cannot persist royal-imports ({refusal})")
1162
+ return
1163
  from harness import runtime as _runtime
1164
  rt = _runtime.get_runtime("royal-imports")
1165
  routes_customers.warm_default(rt)
api/routes_automation.py CHANGED
@@ -1745,6 +1745,12 @@ async def automation_hook(auto_id: str, token: str, request: Request):
1745
  "message": "no automation with that id and token"})
1746
  for slug in _rt.known_tenants():
1747
  try:
 
 
 
 
 
 
1748
  rt = _rt.get_runtime(slug)
1749
  except Exception: # noqa: BLE001
1750
  continue
 
1745
  "message": "no automation with that id and token"})
1746
  for slug in _rt.known_tenants():
1747
  try:
1748
+ # A non-production Pg Space may display tenant #0 but can never persist an
1749
+ # automation webhook there. Skip before building its runtime, exactly as the
1750
+ # scheduler/tick path does; a skipped tenant remains indistinguishable from an
1751
+ # unknown hook to this unauthenticated caller.
1752
+ if engine._pg_tick_refusal(slug):
1753
+ continue
1754
  rt = _rt.get_runtime(slug)
1755
  except Exception: # noqa: BLE001
1756
  continue