@tokenoftrust/cli 2.4.5 → 2.4.6

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/cli",
3
- "version": "2.4.5",
3
+ "version": "2.4.6",
4
4
  "description": "Token of Trust developer CLI — clone a tenant store, run it locally with save→reload, and submit it for preview. Installs the `tot` command.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Token of Trust",
@@ -1101,16 +1101,13 @@ export async function submitCandidate(scoped, { repo, changeId, ref, headSha, ch
1101
1101
  return result;
1102
1102
  } catch (e) {
1103
1103
  if (e instanceof CandidateRegistrationError) throw e;
1104
- // "502 despite success" (migration DAG §U4): candidate_open's forge write is
1105
- // idempotent and may have LANDED even though its HTTP response never reached us
1106
- // (a 5xx between MCP and its own caller, a dropped connection, …). Before reporting
1107
- // a fatal "candidate not created," re-read it by its stable changeId — if it's
1108
- // already there with the exact ref/head we just pushed, this submit DID succeed;
1109
- // report that instead of a false failure. Purely a recovery path: any error from
1110
- // the re-read (including "not found," which is the ordinary case for a genuine
1111
- // registration failure) falls through to the original fatal error, unchanged.
1112
- const httpStatus = /** @type {any} */ (e)?.diagnostic?.httpStatus;
1113
- if (typeof httpStatus === "number" && httpStatus >= 500) {
1104
+ // candidate_open's forge write is idempotent and may have LANDED even though its
1105
+ // response never reached us (a gateway 5xx, a dropped connection, our own timeout).
1106
+ // Before reporting a fatal "candidate not created," re-read it by its stable changeId
1107
+ // — if it's already there with the exact ref/head we just pushed, this submit DID
1108
+ // succeed. Any error from the re-read (including "not found," the ordinary case for a
1109
+ // genuine registration failure) falls through to the original fatal error, unchanged.
1110
+ if (responseLost(e)) {
1114
1111
  const existing = await fetchCandidateStatus(scoped, { repo, changeId }).catch(() => null);
1115
1112
  if (
1116
1113
  existing
@@ -1127,6 +1124,14 @@ export async function submitCandidate(scoped, { repo, changeId, ref, headSha, ch
1127
1124
  }
1128
1125
  }
1129
1126
 
1127
+ /** True when a call's outcome is unknown — the server may have acted, but its answer
1128
+ * never arrived — as opposed to a refusal the server actually sent. */
1129
+ function responseLost(error) {
1130
+ const d = /** @type {any} */ (error)?.diagnostic;
1131
+ if (typeof d?.httpStatus === "number" && d.httpStatus >= 500) return true;
1132
+ return d?.category === "network" || d?.category === "timeout";
1133
+ }
1134
+
1130
1135
  /** Print the candidate_open result: the PR the approver reviews, or the MCP's own
1131
1136
  * refusal message when it couldn't open/update one. `quiet` (--json) suppresses it.
1132
1137
  * Deliberately does NOT print `result.url` — that's the INTERNAL forge (Gitea) PR