@tokenoftrust/cli 4.11.4 → 4.12.1

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": "4.11.4",
3
+ "version": "4.12.1",
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",
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Tenant binaries never live in the tenant repo. The platform's content-addressed `/_a/<sha256>`
3
- * pool owns them and the store's repo holds references only. Platform capture of catalog images
4
- * is pending: until it owns them, an image stays at its source URL, go-live requires 0 uncaptured
5
- * catalog images, and the source store is never shut down before that check passes. This decides,
3
+ * pool owns them and the store's repo holds references only. Publication captures source images;
4
+ * go-live requires zero uncaptured images in every type and sampled live owned-image responses
5
+ * before the source store is shut down. This decides,
6
6
  * from a listing of the store's `public/` tree, whether the repo is carrying binaries it must not.
7
7
  *
8
8
  * Findings, each naming its remedy:
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * An invited developer holds a `tot login` MCP session but no operator secret and no
5
5
  * storefront cookie. This discovers
6
- * the storefront's trusted issuer without credentials, then mints a VIEWER session
6
+ * the storefront's trusted issuers without credentials, then mints a VIEWER session
7
7
  * from the developer's matching MCP token (POST
8
8
  * /api/dev/cli-session on the tenant's own host, which resolves WHO the opaque token
9
9
  * is via the MCP and hands back a `tot_session` cookie), then hands the caller a
@@ -55,42 +55,20 @@ function originOf(url) {
55
55
  }
56
56
  }
57
57
 
58
- /** The hosted storefront Worker's own public origins (`[env.preview]`). Only a
59
- * storefront called at one of these EXACT origins may steer the CLI to present a
60
- * session for a different MCP issuer: anything reached through `--url` /
61
- * `TOT_STOREFRONT_URL` is user input, and a hostile one could otherwise name the
62
- * prod issuer and collect that session. */
63
- export const FIRST_PARTY_STOREFRONT_ORIGINS = Object.freeze(new Set([
64
- DEFAULT_STOREFRONT_ORIGIN,
65
- "https://storefront.tokenoftrust.workers.dev",
66
- ]));
67
-
68
- /** The Token of Trust MCP issuers a first-party storefront may name. Exact origins. */
58
+ /** Exact identity issuers supported by hosted storefront sessions. */
69
59
  export const TOKEN_OF_TRUST_MCP_ORIGINS = Object.freeze(new Set([
70
60
  "https://mcp.tokenoftrust.com",
71
61
  "https://mcp.qa.tokenoftrust.com",
72
62
  ]));
73
63
 
74
- /** True only when BOTH sides are declared first-party, by exact origin. */
75
- function mayPresentSessionFor(storefrontBase, issuer) {
76
- const storefront = originOf(storefrontBase);
77
- return Boolean(storefront && issuer &&
78
- FIRST_PARTY_STOREFRONT_ORIGINS.has(storefront) && TOKEN_OF_TRUST_MCP_ORIGINS.has(issuer));
79
- }
80
-
81
64
  /**
82
65
  * Turn a non-2xx `/api/dev/cli-session` response into one clean, actionable
83
66
  * `{ message, hint }` — the ONE place this CLI interprets that endpoint's refusal
84
67
  * shape, so ship/preview-token (both routed through `resolveViewerTransport`)
85
68
  * never duplicate or drift from each other.
86
69
  *
87
- * - 401 `unresolved_token` carrying `trustedMcp`, where the CLI's OWN signed-in
88
- * MCP (`signedInMcpUrl`, from `resolveDeveloperSession`) is a DIFFERENT origin
89
- * — the genuine tier mismatch (signed in to qa, store trusts prod, or vice
90
- * versa): say exactly which two origins disagree and the exact fix.
91
- * - Any other 401 — a real "you're not recognized here" — keeps the plain
92
- * login hint, but still surfaces the server's error code so a report isn't
93
- * just "it didn't work".
70
+ * - 401 keeps recovery on the CLI's selected issuer. Discovery or refusal payloads
71
+ * never select another credential or steer sign-in to another authority.
94
72
  * - 502/503 — the SEAM is broken (unconfigured, unreachable, malformed), which
95
73
  * is never a login problem: say so, with the server's code, and suggest a
96
74
  * retry instead of `tot login`.
@@ -103,7 +81,6 @@ function mayPresentSessionFor(storefrontBase, issuer) {
103
81
  export function describeCliSessionRefusal(status, body, signedInMcpUrl) {
104
82
  const error = body && typeof body.error === "string" ? body.error : null;
105
83
  const code = body && typeof body.code === "string" ? body.code : null;
106
- const trustedMcp = body && typeof body.trustedMcp === "string" ? body.trustedMcp : null;
107
84
 
108
85
  if (status === 502 || status === 503) {
109
86
  return {
@@ -112,23 +89,13 @@ export function describeCliSessionRefusal(status, body, signedInMcpUrl) {
112
89
  };
113
90
  }
114
91
 
115
- if (error === "unresolved_token" && trustedMcp && TOKEN_OF_TRUST_MCP_ORIGINS.has(trustedMcp)) {
116
- const signedInOrigin = originOf(signedInMcpUrl);
117
- if (signedInOrigin && signedInOrigin !== trustedMcp) {
118
- // `TOT_SELECTED_MCP_URL=… tot login` adds a session for the storefront's MCP
119
- // WITHOUT rebinding this checkout (`tot login --mcp` inside a checkout would
120
- // move every command there to the other tier). Once that session exists,
121
- // `resolveViewerTransport` uses it for the hosted storefront on its own.
122
- return {
123
- message: `you're signed in to ${signedInOrigin}, but the hosted storefront trusts ${trustedMcp}.`,
124
- hint: `sign in to it once with \`TOT_SELECTED_MCP_URL=${trustedMcp} tot login\` (this checkout stays on ${signedInOrigin}), then re-run.`,
125
- };
126
- }
127
- }
92
+ const selectedMcp = originOf(signedInMcpUrl);
93
+ const login = selectedMcp && TOKEN_OF_TRUST_MCP_ORIGINS.has(selectedMcp)
94
+ ? `TOT_SELECTED_MCP_URL=${selectedMcp} tot login` : "tot login";
128
95
 
129
96
  return {
130
97
  message: `your \`tot\` session wasn't recognized for this store${error && error !== "unresolved_token" ? ` (${error})` : ""}.`,
131
- hint: "run `tot login`, then re-run.",
98
+ hint: `run \`${login}\`, then re-run.`,
132
99
  };
133
100
  }
134
101
 
@@ -200,24 +167,17 @@ export async function resolveViewerTransport({
200
167
  hint: "retry shortly; if it persists, report it with `tot feedback`." };
201
168
  }
202
169
  const advertised = await readJson(discovery);
203
- const trustedMcp = typeof advertised?.trustedMcp === "string" ? advertised.trustedMcp : null;
204
- if (!trustedMcp || !TOKEN_OF_TRUST_MCP_ORIGINS.has(trustedMcp)) {
170
+ const trustedMcps = advertised?.trustedMcps;
171
+ if (!Array.isArray(trustedMcps) || !trustedMcps.length ||
172
+ !trustedMcps.every((issuer) => typeof issuer === "string" && TOKEN_OF_TRUST_MCP_ORIGINS.has(issuer))) {
205
173
  return { ok: false, ...describeCliSessionRefusal(503, { code: "invalid_mcp_issuer" }, dev.mcpUrl) };
206
174
  }
207
- if (originOf(dev.mcpUrl) !== trustedMcp) {
208
- /** @type {{ ok: false, message: string, hint: string }} */
209
- const refusal = { ok: false, ...describeCliSessionRefusal(401, { error: "unresolved_token", trustedMcp }, dev.mcpUrl) };
210
- if (!mayPresentSessionFor(base, trustedMcp)) return refusal;
211
- let trustedDev = null;
212
- try {
213
- trustedDev = await resolveDev(null, { ...env, TOT_SELECTED_MCP_URL: trustedMcp }, { fetchImpl });
214
- } catch {
215
- /* No usable credential for the declared issuer: nothing is presented. */
216
- }
217
- if (!trustedDev?.token || originOf(trustedDev.mcpUrl) !== trustedMcp) return refusal;
218
- dev = trustedDev;
175
+ const selectedMcp = originOf(dev.mcpUrl);
176
+ if (!selectedMcp || !trustedMcps.includes(selectedMcp)) {
177
+ return { ok: false, message: "your selected MCP issuer isn't trusted by this store.",
178
+ hint: "report it with `tot feedback`; this command keeps your selected issuer." };
219
179
  }
220
- const mintedSession = await mintSession(base, dev.token, fetchImpl, vlog);
180
+ const mintedSession = await mintSession(base, dev.token, selectedMcp, fetchImpl, vlog);
221
181
  if (!mintedSession.ok) return mintedSession.refusal;
222
182
  const res = mintedSession.response;
223
183
 
@@ -300,7 +260,7 @@ async function readJson(res) {
300
260
  * POST the bearer to `<base>/api/dev/cli-session`.
301
261
  * @returns {Promise<{ ok: true, response: any } | { ok: false, refusal: { ok: false, message: string, hint: string } }>}
302
262
  */
303
- async function mintSession(base, token, fetchImpl, vlog) {
263
+ async function mintSession(base, token, mcpOrigin, fetchImpl, vlog) {
304
264
  let res;
305
265
  try {
306
266
  // `content-type: application/json` is REQUIRED, not decoration. Astro's origin
@@ -320,7 +280,7 @@ async function mintSession(base, token, fetchImpl, vlog) {
320
280
  authorization: `Bearer ${token}`,
321
281
  "content-type": "application/json",
322
282
  },
323
- body: "{}",
283
+ body: JSON.stringify({ mcpOrigin }),
324
284
  // Never follow a redirect with the bearer attached: a 3xx is a refusal here
325
285
  // (non-2xx), so the token only ever reaches the origin it was addressed to.
326
286
  redirect: "manual",