@xuda.io/account_module 1.2.2288 → 1.2.2289

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.mjs +51 -14
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -1990,11 +1990,29 @@ const _srv_role = (doc) => {
1990
1990
  return 'other';
1991
1991
  };
1992
1992
 
1993
+ // xuda_servers is authoritative on the dev orchestrator and replicates dev->master
1994
+ // ONE-WAY (db_module.get_live_servers_couch): nothing replicates back INTO dev, so
1995
+ // a stat:1 enqueue written on any other node's local couch strands and the dev
1996
+ // rollout tick never sees it. So the ops server methods (list / status / deploy)
1997
+ // must read+write the ORCHESTRATOR's couch, not the local region replica. On dev
1998
+ // itself this is the local connection (unchanged); from any other node we reach
1999
+ // dev's couch over the WG mesh, reusing THIS node's couch admin credential (shared
2000
+ // fleet-wide). Endpoint comes from _conf.deploy_orchestrator; absent -> fall back
2001
+ // to local (no worse than before, and never throws on nodes not yet reconfigured).
2002
+ const _orchestrator_servers_couch = () => {
2003
+ const orch = _conf.deploy_orchestrator || {};
2004
+ const host = process.env.XUDA_HOSTNAME;
2005
+ if (!orch.couch_location || host === orch.hostname) return db_module.get_con('xuda_servers');
2006
+ const local = (_conf.couchdb_control_database && _conf.couchdb_control_database[host]) || {};
2007
+ const url = `${local.db_server_http || 'http'}://${local.db_server_username || 'admin'}:${local.db_server_password || ''}@${orch.couch_location}/`;
2008
+ return db_module.get_con('xuda_servers', url);
2009
+ };
2010
+
1993
2011
  // Load every xuda_servers doc via _all_docs (nano .list) rather than a
1994
2012
  // match-all Mango selector — avoids the "no matching index" warning that would
1995
2013
  // otherwise spam the log on every (frequently-polled) status call.
1996
2014
  const _load_all_server_docs = async () => {
1997
- const couch = await db_module.get_con('xuda_servers');
2015
+ const couch = _orchestrator_servers_couch();
1998
2016
  const res = await couch.list({ include_docs: true });
1999
2017
  return (res.rows || []).map((r) => r.doc).filter((d) => d && !String(d._id).startsWith('_design'));
2000
2018
  };
@@ -2063,12 +2081,26 @@ export const ops_deploy_server = async function (req = {}) {
2063
2081
  if (!server_id) return { code: -1, data: 'server_id is required' };
2064
2082
  if (server_id === 'dev') return { code: -1, data: 'the dev orchestrator deploys via rsync, not this rollout; pick a region (master/us/eu)' };
2065
2083
 
2066
- // FULL doc (no field projection) so it is safe to save back.
2067
- const sr = await db_module.get_couch_doc('xuda_servers', server_id);
2068
- if (sr.code < 0 || !sr.data) return { code: -1, data: `server "${server_id}" not found` };
2069
- const doc = sr.data;
2084
+ // Read + write the ORCHESTRATOR's authoritative couch (dev), NOT the local
2085
+ // region replica: a stat:1 written to a region's copy never reaches the dev
2086
+ // rollout tick (nothing replicates back into dev), so it would strand and the
2087
+ // deploy would silently no-op. Raw nano .get/.insert (full doc, safe to save
2088
+ // back) mirrors _publish_rollout's own access pattern.
2089
+ const couch = _orchestrator_servers_couch();
2090
+ let doc;
2091
+ try {
2092
+ doc = await couch.get(server_id);
2093
+ } catch (e) {
2094
+ return { code: -1, data: e.statusCode === 404 ? `server "${server_id}" not found` : `orchestrator couch read failed: ${e.message}` };
2095
+ }
2070
2096
  if (!_srv_is_deployable(doc)) return { code: -1, data: `server "${server_id}" is not a deployable region app-server (needs docType 'regional_server' + application_server)` };
2071
2097
 
2098
+ // Preview (dry_run) writes nothing and needs no confirmation, so it must run
2099
+ // BEFORE the confirmation and busy gates: "Preview plan" sends dry_run:true
2100
+ // with no confirm, and should also work while a deploy is already in flight.
2101
+ const plan = { server_id, mode, current_state: _srv_deploy_state(doc), busy: _srv_busy(doc), private_ip: doc.private || null };
2102
+ if (req.dry_run) return { code: 1, data: { dry_run: true, would_deploy: plan } };
2103
+
2072
2104
  // Confirmation gate — proportional to blast radius.
2073
2105
  if (mode === 'hard') {
2074
2106
  if (req.confirm !== server_id) return { code: -1, data: `hard deploy tears down and reinstalls "${server_id}" (brief downtime). Re-send with confirm:"${server_id}" to proceed.` };
@@ -2080,18 +2112,18 @@ export const ops_deploy_server = async function (req = {}) {
2080
2112
  // Already-in-flight guard.
2081
2113
  if (_srv_busy(doc)) return { code: -1, data: `a deploy is already ${_srv_deploy_state(doc)} for "${server_id}"; wait for it to finish (poll ops_server_deploy_status).` };
2082
2114
 
2083
- const plan = { server_id, mode, current_state: _srv_deploy_state(doc), private_ip: doc.private || null };
2084
- if (req.dry_run) return { code: 1, data: { dry_run: true, would_deploy: plan } };
2085
-
2086
- // Enqueue: the dev orchestrator's 1-min publish tick picks stat:1 up.
2115
+ // Enqueue on the orchestrator's couch: the dev 1-min publish tick picks stat:1 up.
2087
2116
  doc.stat = 1;
2088
2117
  doc.stat_ts = Date.now();
2089
2118
  doc.publish_mode = mode;
2090
2119
  doc.deploy_requested_by = req.uid;
2091
2120
  doc.deploy_requested_ts = Date.now();
2092
2121
  delete doc.publish_error;
2093
- const wr = await db_module.save_couch_doc('xuda_servers', doc);
2094
- if (wr.code < 0) return { code: -1, data: wr.data };
2122
+ try {
2123
+ await couch.insert(doc);
2124
+ } catch (e) {
2125
+ return { code: -1, data: `enqueue write to orchestrator couch failed: ${e.message}` };
2126
+ }
2095
2127
 
2096
2128
  _ops_notify_ops(`Server deploy queued (${mode}): ${server_id}`, [['Server', server_id], ['Mode', mode], ['By', req.uid], ['State', 'queued'], ['Orchestrator', 'dev.xuda.ai']], 'OPS-DEPLOY-' + server_id);
2097
2129
  logs_msa.add_account_log({ uid: req.uid, method: 'ops_deploy_server', source: 'ops dashboard', response_status_code: 200, response_data: `queued ${mode} deploy for ${server_id} by ${req.uid}` });
@@ -2120,9 +2152,14 @@ export const ops_server_deploy_status = async function (req = {}) {
2120
2152
  });
2121
2153
 
2122
2154
  if (req.server_id) {
2123
- const sr = await db_module.get_couch_doc('xuda_servers', String(req.server_id).trim());
2124
- if (sr.code < 0 || !sr.data) return { code: -1, data: `server "${req.server_id}" not found` };
2125
- return { code: 1, data: to_status(sr.data) };
2155
+ // Read the orchestrator's authoritative copy so progress reflects the dev
2156
+ // rollout's stat writes (the region replica can lag / never see enqueues).
2157
+ try {
2158
+ const doc = await _orchestrator_servers_couch().get(String(req.server_id).trim());
2159
+ return { code: 1, data: to_status(doc) };
2160
+ } catch (e) {
2161
+ return { code: -1, data: e.statusCode === 404 ? `server "${req.server_id}" not found` : `orchestrator couch read failed: ${e.message}` };
2162
+ }
2126
2163
  }
2127
2164
  const docs = await _load_all_server_docs();
2128
2165
  const rows = docs.filter(_srv_is_deployable).map(to_status);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xuda.io/account_module",
3
- "version": "1.2.2288",
3
+ "version": "1.2.2289",
4
4
  "description": "Xuda Account Server Module",
5
5
  "main": "index.mjs",
6
6
  "dependencies": {