@mindful-web/marko-web-identity-x 1.80.0 → 1.80.2

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/CLAUDE.md CHANGED
@@ -94,7 +94,21 @@ segment — a trace is the only place a call still *in flight* when its transact
94
94
  record. Same treatment on the Omeda client in `marko-web-omeda/middleware/graphql-client.js`
95
95
  (20s, `OMEDA_REQUEST_TIMEOUT_MS`), which had the identical no-timeout defect and sits on the blocking
96
96
  path of authentication. To compare upstreams:
97
- `SELECT count(*), average(ms), percentile(ms, 95), max(ms) FROM MindfulApiCall FACET api, operation, outcome`.
97
+ `SELECT sum(sampleRate) FROM MindfulApiCall FACET api, operation`.
98
+
99
+ **`MindfulApiCall` is sampled.** Unsampled it ran ~55k events/hour / ~40M per month on the Fusable
100
+ fleet, 88.9% of it `GetActiveAppContext` + `CheckContentAccess` — per-page-load context calls that
101
+ are uniformly fast and analytically useless. Kept: **every non-`ok` outcome**, **every `ok` call
102
+ >= `MINDFUL_API_CALL_SLOW_MS`** (default 250ms — deliberately under Omeda's observed p95 of 282ms, so
103
+ ordinary Omeda latency stays visible while fast idx calls fall away), and **1 in
104
+ `MINDFUL_API_CALL_SAMPLE_RATE`** (default 100) of the rest. Each event carries `sampleRate` = the
105
+ number of real calls it represents, so `sum(sampleRate)` recovers true volume; `sampleRate = 1` means
106
+ the call was kept because it failed or was slow.
107
+
108
+ Consequences when querying: `average(ms)` over everything **skews high** (fast calls are
109
+ under-represented by design) — filter `WHERE sampleRate = 1` for the error/slow tail, or
110
+ `WHERE sampleRate > 1` for a fast-call view. Set `MINDFUL_API_CALL_SAMPLE_RATE=1` to record
111
+ everything again during an investigation, no deploy required.
98
112
 
99
113
  It wraps `client.query`/`client.mutate`, **not** `fetch` — `@parameter1/omeda-graphql-client` sets
100
114
  `fetch` *after* spreading the caller's `linkConfig`, so a wrapped transport cannot be injected from
@@ -150,6 +164,26 @@ so it bails, writes no `autoSignups`, and **no `subscribe` conversion is ever re
150
164
  `idxAutoSignupCount: 0` seen on every completed One Tap sign-in on overdriveonline.com. Safe to await
151
165
  only because the Omeda client now has a request timeout.
152
166
 
167
+ **The name backfill is non-fatal, and must stay that way.** `GoogleSignInUpdateUser`
168
+ (`updateOwnAppUser`) is the **only** call in this route authenticated as the *user* rather than the
169
+ org — every other one passes `context: { apiToken: getOrgUserApiToken() }` — so it is the only one
170
+ depending on the session token minted moments earlier already being readable. Production shows it
171
+ returning *"The provided value is not a valid token"*, while the same token value works on the very
172
+ next request arriving from the cookie: a read-after-write race on the idx token store. Retried once
173
+ for that reason.
174
+
175
+ Non-fatal matters more than the retry. It backfills a first and last name — cosmetic — but
176
+ `tokenCookie.setTo` has **already run**, so the user is authenticated by the time it executes.
177
+ Throwing turned a fully successful sign-in into a 500, and the client, seeing `!data.ok`, ran `fail()`
178
+ and redirected a *logged-in* user to `/user/login`. A missing display name must never cost someone
179
+ their session. Failures report through `onHookError` and the route continues.
180
+
181
+ `FACET idxNameBackfill` (`ok` | `retried` | `failed`) distinguishes the two bugs that share this one
182
+ error message: if `retried` dominates it is the race; if `failed` dominates the impersonation token
183
+ simply cannot authorize this mutation, and the fix is to stop using it — backfill before impersonation
184
+ via `setUnverifiedAppUserData` (org-token, but writes *unverified* data, so it is a semantic change
185
+ worth deciding deliberately).
186
+
153
187
  `idxGoogleStepMs` records elapsed ms at the start of each step, making the route self-profiling:
154
188
  `FACET idxGoogleStep SELECT max(idxGoogleStepMs)` shows where the ~12s on a *successful* sign-in
155
189
  goes, and a request that dies mid-flight still reports how far it got. Responds `{ ok, requiresUserInput,
@@ -218,7 +252,8 @@ meter / gate.
218
252
  **Telemetry.** Two seams. Neither needs site configuration:
219
253
  - `config.addRequestAttributes` — called with a flat object as the route progresses
220
254
  (`idxGoogleStep`, `idxUserId`, `idxCreatedNewUser`, `idxRequiresUserInput`, `idxGoogleSurface`,
221
- `idxGoogleMissingFieldsBehavior`, `idxAutoSignupCount`, `idxImpersonateAttempts`). **Defaults to `utils/add-custom-attributes.js`**, which tags
255
+ `idxGoogleMissingFieldsBehavior`, `idxAutoSignupCount`, `idxImpersonateAttempts`,
256
+ `idxNameBackfill`, `idxNameBackfillAttempts`). **Defaults to `utils/add-custom-attributes.js`**, which tags
222
257
  the current New Relic transaction, so the attributes ride on the `Transaction` *and* any
223
258
  `TransactionError` it produces — a 500 says which step failed instead of being a bare GraphQL
224
259
  message. That default resolves the agent **only if the app already loaded it** (checks
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.0/components/access.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.2/components/access.marko",
6
6
  marko_component = require("./access.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_objectPath_module = require("@mindful-web/object-path"),
@@ -53,7 +53,7 @@ marko_template._ = marko_renderer(render, {
53
53
  }, marko_component);
54
54
 
55
55
  marko_template.meta = {
56
- id: "/@mindful-web/marko-web-identity-x$1.80.0/components/access.marko",
56
+ id: "/@mindful-web/marko-web-identity-x$1.80.2/components/access.marko",
57
57
  component: "./access.marko",
58
58
  tags: [
59
59
  "@mindful-web/marko-core/components/resolve.marko"
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.0/components/comment-stream.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.2/components/comment-stream.marko",
6
6
  marko_component = require("./comment-stream.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_objectPath_module = require("@mindful-web/object-path"),
@@ -91,7 +91,7 @@ marko_template._ = marko_renderer(render, {
91
91
  }, marko_component);
92
92
 
93
93
  marko_template.meta = {
94
- id: "/@mindful-web/marko-web-identity-x$1.80.0/components/comment-stream.marko",
94
+ id: "/@mindful-web/marko-web-identity-x$1.80.2/components/comment-stream.marko",
95
95
  component: "./comment-stream.marko",
96
96
  tags: [
97
97
  "@mindful-web/marko-web/components/browser-component.marko",
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.0/components/context.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.2/components/context.marko",
6
6
  marko_component = require("./context.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  marko_dynamicTag = require("marko/dist/runtime/helpers/dynamic-tag"),
@@ -49,7 +49,7 @@ marko_template._ = marko_renderer(render, {
49
49
  }, marko_component);
50
50
 
51
51
  marko_template.meta = {
52
- id: "/@mindful-web/marko-web-identity-x$1.80.0/components/context.marko",
52
+ id: "/@mindful-web/marko-web-identity-x$1.80.2/components/context.marko",
53
53
  component: "./context.marko",
54
54
  tags: [
55
55
  "@mindful-web/marko-core/components/resolve.marko"
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.0/components/form-access.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.2/components/form-access.marko",
6
6
  marko_component = require("./form-access.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_defaultValue = require("@mindful-web/marko-core/utils/default-value"),
@@ -179,7 +179,7 @@ marko_template._ = marko_renderer(render, {
179
179
  }, marko_component);
180
180
 
181
181
  marko_template.meta = {
182
- id: "/@mindful-web/marko-web-identity-x$1.80.0/components/form-access.marko",
182
+ id: "/@mindful-web/marko-web-identity-x$1.80.2/components/form-access.marko",
183
183
  component: "./form-access.marko",
184
184
  tags: [
185
185
  "@mindful-web/marko-web/components/browser-component.marko",
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.0/components/form-authenticate.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.2/components/form-authenticate.marko",
6
6
  marko_component = require("./form-authenticate.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_objectPath_module = require("@mindful-web/object-path"),
@@ -81,7 +81,7 @@ marko_template._ = marko_renderer(render, {
81
81
  }, marko_component);
82
82
 
83
83
  marko_template.meta = {
84
- id: "/@mindful-web/marko-web-identity-x$1.80.0/components/form-authenticate.marko",
84
+ id: "/@mindful-web/marko-web-identity-x$1.80.2/components/form-authenticate.marko",
85
85
  component: "./form-authenticate.marko",
86
86
  tags: [
87
87
  "@mindful-web/marko-web/components/browser-component.marko",
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.0/components/form-change-email.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.2/components/form-change-email.marko",
6
6
  marko_component = require("./form-change-email.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_objectPath_module = require("@mindful-web/object-path"),
@@ -59,7 +59,7 @@ marko_template._ = marko_renderer(render, {
59
59
  }, marko_component);
60
60
 
61
61
  marko_template.meta = {
62
- id: "/@mindful-web/marko-web-identity-x$1.80.0/components/form-change-email.marko",
62
+ id: "/@mindful-web/marko-web-identity-x$1.80.2/components/form-change-email.marko",
63
63
  component: "./form-change-email.marko",
64
64
  tags: [
65
65
  "@mindful-web/marko-web/components/browser-component.marko",
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.0/components/form-login.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.2/components/form-login.marko",
6
6
  marko_component = require("./form-login.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_objectPath_module = require("@mindful-web/object-path"),
@@ -92,7 +92,7 @@ marko_template._ = marko_renderer(render, {
92
92
  }, marko_component);
93
93
 
94
94
  marko_template.meta = {
95
- id: "/@mindful-web/marko-web-identity-x$1.80.0/components/form-login.marko",
95
+ id: "/@mindful-web/marko-web-identity-x$1.80.2/components/form-login.marko",
96
96
  component: "./form-login.marko",
97
97
  tags: [
98
98
  "@mindful-web/marko-web/components/browser-component.marko",
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.0/components/form-logout.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.2/components/form-logout.marko",
6
6
  marko_renderer = require("marko/dist/runtime/components/renderer"),
7
7
  module_defaultValue = require("@mindful-web/marko-core/utils/default-value"),
8
8
  defaultValue = module_defaultValue.default || module_defaultValue,
@@ -29,7 +29,7 @@ marko_template._ = marko_renderer(render, {
29
29
  });
30
30
 
31
31
  marko_template.meta = {
32
- id: "/@mindful-web/marko-web-identity-x$1.80.0/components/form-logout.marko",
32
+ id: "/@mindful-web/marko-web-identity-x$1.80.2/components/form-logout.marko",
33
33
  tags: [
34
34
  "@mindful-web/marko-web/components/browser-component.marko"
35
35
  ]
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.0/components/form-profile.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.2/components/form-profile.marko",
6
6
  marko_component = require("./form-profile.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_objectPath_module = require("@mindful-web/object-path"),
@@ -84,7 +84,7 @@ marko_template._ = marko_renderer(render, {
84
84
  }, marko_component);
85
85
 
86
86
  marko_template.meta = {
87
- id: "/@mindful-web/marko-web-identity-x$1.80.0/components/form-profile.marko",
87
+ id: "/@mindful-web/marko-web-identity-x$1.80.2/components/form-profile.marko",
88
88
  component: "./form-profile.marko",
89
89
  tags: [
90
90
  "@mindful-web/marko-web/components/browser-component.marko",
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.0/components/form-register.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.2/components/form-register.marko",
6
6
  marko_renderer = require("marko/dist/runtime/components/renderer"),
7
7
  marko_assign = require("marko/dist/runtime/helpers/assign"),
8
8
  marko_web_identity_x_form_login_template = require("./form-login.marko"),
@@ -25,7 +25,7 @@ marko_template._ = marko_renderer(render, {
25
25
  });
26
26
 
27
27
  marko_template.meta = {
28
- id: "/@mindful-web/marko-web-identity-x$1.80.0/components/form-register.marko",
28
+ id: "/@mindful-web/marko-web-identity-x$1.80.2/components/form-register.marko",
29
29
  tags: [
30
30
  "./form-login.marko"
31
31
  ]
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.0/components/google-init.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.2/components/google-init.marko",
6
6
  marko_renderer = require("marko/dist/runtime/components/renderer"),
7
7
  marko_attr = require("marko/dist/runtime/html/helpers/attr"),
8
8
  marko_str = require("marko/dist/runtime/helpers/to-string");
@@ -239,5 +239,5 @@ marko_template._ = marko_renderer(render, {
239
239
  });
240
240
 
241
241
  marko_template.meta = {
242
- id: "/@mindful-web/marko-web-identity-x$1.80.0/components/google-init.marko"
242
+ id: "/@mindful-web/marko-web-identity-x$1.80.2/components/google-init.marko"
243
243
  };
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.0/components/google-sign-in-button.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.2/components/google-sign-in-button.marko",
6
6
  marko_renderer = require("marko/dist/runtime/components/renderer"),
7
7
  helpers_escape_xml = require("marko/dist/runtime/html/helpers/escape-xml"),
8
8
  marko_escapeXml = helpers_escape_xml.x,
@@ -40,5 +40,5 @@ marko_template._ = marko_renderer(render, {
40
40
  });
41
41
 
42
42
  marko_template.meta = {
43
- id: "/@mindful-web/marko-web-identity-x$1.80.0/components/google-sign-in-button.marko"
43
+ id: "/@mindful-web/marko-web-identity-x$1.80.2/components/google-sign-in-button.marko"
44
44
  };
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.0/components/identify.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.2/components/identify.marko",
6
6
  marko_component = require("./identify.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_objectPath_module = require("@mindful-web/object-path"),
@@ -50,7 +50,7 @@ marko_template._ = marko_renderer(render, {
50
50
  }, marko_component);
51
51
 
52
52
  marko_template.meta = {
53
- id: "/@mindful-web/marko-web-identity-x$1.80.0/components/identify.marko",
53
+ id: "/@mindful-web/marko-web-identity-x$1.80.2/components/identify.marko",
54
54
  component: "./identify.marko",
55
55
  tags: [
56
56
  "@mindful-web/marko-web-gtm/components/push.marko",
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.0/components/non-auth-identify.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.2/components/non-auth-identify.marko",
6
6
  marko_component = require("./non-auth-identify.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  marko_dynamicTag = require("marko/dist/runtime/helpers/dynamic-tag"),
@@ -45,7 +45,7 @@ marko_template._ = marko_renderer(render, {
45
45
  }, marko_component);
46
46
 
47
47
  marko_template.meta = {
48
- id: "/@mindful-web/marko-web-identity-x$1.80.0/components/non-auth-identify.marko",
48
+ id: "/@mindful-web/marko-web-identity-x$1.80.2/components/non-auth-identify.marko",
49
49
  component: "./non-auth-identify.marko",
50
50
  tags: [
51
51
  "@mindful-web/marko-core/components/resolve.marko"
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  var marko_template = module.exports = require("marko/dist/html").t(__filename),
5
- marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.0/components/subscribe.marko",
5
+ marko_componentType = "/@mindful-web/marko-web-identity-x$1.80.2/components/subscribe.marko",
6
6
  marko_component = require("./subscribe.marko"),
7
7
  marko_renderer = require("marko/dist/runtime/components/renderer"),
8
8
  module_objectPath_module = require("@mindful-web/object-path"),
@@ -73,7 +73,7 @@ marko_template._ = marko_renderer(render, {
73
73
  }, marko_component);
74
74
 
75
75
  marko_template.meta = {
76
- id: "/@mindful-web/marko-web-identity-x$1.80.0/components/subscribe.marko",
76
+ id: "/@mindful-web/marko-web-identity-x$1.80.2/components/subscribe.marko",
77
77
  component: "./subscribe.marko",
78
78
  tags: [
79
79
  "@mindful-web/marko-web/components/browser-component.marko",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindful-web/marko-web-identity-x",
3
- "version": "1.80.0",
3
+ "version": "1.80.2",
4
4
  "description": "Marko Wrapper for IdentityX",
5
5
  "repository": "https://github.com/parameter1/mindful-web/tree/main/packages/marko-web-identity-x",
6
6
  "author": "Josh Worden <josh@parameter1.com>",
@@ -13,9 +13,9 @@
13
13
  "test": "yarn compile --no-clean && yarn lint"
14
14
  },
15
15
  "dependencies": {
16
- "@mindful-web/marko-web-gtm": "^1.80.0",
17
- "@mindful-web/object-path": "^1.80.0",
18
- "@mindful-web/utils": "^1.80.0",
16
+ "@mindful-web/marko-web-gtm": "^1.80.2",
17
+ "@mindful-web/object-path": "^1.80.2",
18
+ "@mindful-web/utils": "^1.80.2",
19
19
  "apollo-cache-inmemory": "^1.6.6",
20
20
  "apollo-client": "^2.6.10",
21
21
  "apollo-link-context": "^1.0.20",
@@ -38,5 +38,5 @@
38
38
  "publishConfig": {
39
39
  "access": "public"
40
40
  },
41
- "gitHead": "564bbee92cbb8f659f8fc6e7cf8991f8d647dec3"
41
+ "gitHead": "59e2be57d4905ab42c5acfa590c270a8c12bfce7"
42
42
  }
package/routes/google.js CHANGED
@@ -33,6 +33,24 @@ const LOGIN_SOURCE = 'google-one-tap';
33
33
  const IMPERSONATE_MAX_ATTEMPTS = 2;
34
34
  const IMPERSONATE_RETRY_DELAY_MS = 250;
35
35
 
36
+ // Same shape as the impersonation retry, and for the same suspected cause: a
37
+ // read-after-write race, this time on the token the previous step just minted.
38
+ const NAME_BACKFILL_MAX_ATTEMPTS = 2;
39
+ const NAME_BACKFILL_RETRY_DELAY_MS = 250;
40
+
41
+ /**
42
+ * A session token that the idx API does not (yet) recognise.
43
+ *
44
+ * Apollo exposes GraphQL errors both individually on `graphQLErrors` and folded
45
+ * into `error.message` as "GraphQL error: <message>"; check both so this does not
46
+ * depend on which shape the caller happens to see.
47
+ */
48
+ const isFreshTokenError = (e) => {
49
+ if (!e) return false;
50
+ const messages = [e.message, ...asArray(e.graphQLErrors).map((g) => g && g.message)];
51
+ return messages.some((m) => /not a valid token|invalid token|token was not found/i.test(String(m)));
52
+ };
53
+
36
54
  /**
37
55
  * Transport-level failures only — a timeout, reset or DNS blip is worth
38
56
  * repeating; a GraphQL validation or authorization error is not, and repeating
@@ -266,10 +284,55 @@ module.exports = asyncRoute(async (req, res) => {
266
284
  if (familyName && !appUser.familyName) nameInput.familyName = familyName;
267
285
  if (Object.keys(nameInput).length) {
268
286
  setStep('update-own-app-user');
269
- await identityX.client.mutate({
270
- mutation: UPDATE_OWN_APP_USER,
271
- variables: { input: nameInput },
272
- });
287
+ // This is the ONLY call in the route authenticated as the *user* rather than
288
+ // the org — every other one passes `context: { apiToken: getOrgUserApiToken() }`
289
+ // — so it is the only one that depends on the session token minted moments
290
+ // earlier already being readable. That is exactly why it is the only
291
+ // operation reported failing: production shows `GoogleSignInUpdateUser`
292
+ // returning "The provided value is not a valid token".
293
+ //
294
+ // The same token value works on the *next* request, arriving from the cookie
295
+ // set above — which is what makes a read-after-write race on the idx token
296
+ // store the leading explanation, so retry once.
297
+ //
298
+ // Non-fatal regardless of the cause, which matters more than the retry. This
299
+ // backfills a first and last name; it is cosmetic. But `tokenCookie.setTo`
300
+ // has already run, so the user IS authenticated by the time we get here —
301
+ // throwing turned a fully successful sign-in into a 500, and the client, on
302
+ // seeing `!data.ok`, ran `fail()` and redirected a logged-in user to
303
+ // /user/login. A missing display name must never cost someone their session.
304
+ let backfill = 'ok';
305
+ let attempts = 0;
306
+ for (;;) {
307
+ attempts += 1;
308
+ try {
309
+ // eslint-disable-next-line no-await-in-loop
310
+ await identityX.client.mutate({
311
+ mutation: UPDATE_OWN_APP_USER,
312
+ variables: { input: nameInput },
313
+ });
314
+ break;
315
+ } catch (e) {
316
+ const retryable = attempts < NAME_BACKFILL_MAX_ATTEMPTS
317
+ && (isFreshTokenError(e) || isRetryableIdxError(e));
318
+ if (!retryable) {
319
+ backfill = 'failed';
320
+ onError(e, {
321
+ idxGoogleStep: 'update-own-app-user',
322
+ idxUserId: appUser.id,
323
+ idxNameBackfillAttempts: attempts,
324
+ });
325
+ break;
326
+ }
327
+ backfill = 'retried';
328
+ // eslint-disable-next-line no-await-in-loop
329
+ await sleep(NAME_BACKFILL_RETRY_DELAY_MS);
330
+ }
331
+ }
332
+ // Measurable: `FACET idxNameBackfill` separates "the retry rescues it" (a
333
+ // race) from "it always fails" (an impersonation token that simply cannot
334
+ // authorize this mutation) — two different bugs with one error message.
335
+ addAttrs({ idxNameBackfill: backfill, idxNameBackfillAttempts: attempts });
273
336
  }
274
337
 
275
338
  // Load the refreshed user so hooks and field checks have up-to-date data.