@reclaimprotocol/agent 0.1.22 → 0.1.24

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 (163) hide show
  1. package/README.md +55 -20
  2. package/lib/authoring/backend.d.ts +13 -10
  3. package/lib/authoring/tools.js +106 -9
  4. package/lib/cdp/browser-proxy.d.ts +78 -0
  5. package/lib/cdp/browser-proxy.js +186 -0
  6. package/lib/cdp/capture-engine.js +8 -7
  7. package/lib/cdp/client.d.ts +10 -0
  8. package/lib/cdp/geo.d.ts +10 -0
  9. package/lib/cdp/geo.js +36 -0
  10. package/lib/cdp/index.d.ts +1 -0
  11. package/lib/cdp/index.js +1 -0
  12. package/lib/live/cloudflared.d.ts +15 -0
  13. package/lib/live/cloudflared.js +268 -0
  14. package/lib/live/page.d.ts +20 -0
  15. package/lib/live/page.js +134 -0
  16. package/lib/live/tunnel.d.ts +23 -0
  17. package/lib/live/tunnel.js +120 -0
  18. package/lib/live/viewer.d.ts +28 -0
  19. package/lib/live/viewer.js +232 -0
  20. package/lib/mcp/api/operations.gen.js +34 -26
  21. package/lib/mcp/api/to-zod.d.ts +1 -1
  22. package/lib/mcp/api/to-zod.js +1 -1
  23. package/lib/mcp/api/tools.js +1 -1
  24. package/lib/mcp/instructions.d.ts +15 -2
  25. package/lib/mcp/instructions.js +101 -38
  26. package/lib/mcp/start.js +4 -3
  27. package/lib/mcp/tools/agent/analyze.js +5 -1
  28. package/lib/mcp/tools/agent/attach.d.ts +8 -1
  29. package/lib/mcp/tools/agent/attach.js +166 -22
  30. package/lib/mcp/tools/agent/build.d.ts +13 -8
  31. package/lib/mcp/tools/agent/build.js +22 -11
  32. package/lib/mcp/tools/agent/capture.js +5 -1
  33. package/lib/mcp/tools/agent/eval.js +3 -1
  34. package/lib/mcp/tools/agent/find.js +6 -5
  35. package/lib/mcp/tools/agent/how-it-works.d.ts +15 -7
  36. package/lib/mcp/tools/agent/how-it-works.js +57 -17
  37. package/lib/mcp/tools/agent/live-view.d.ts +21 -0
  38. package/lib/mcp/tools/agent/live-view.js +153 -0
  39. package/lib/mcp/tools/agent/pod-proof.d.ts +8 -0
  40. package/lib/mcp/tools/agent/pod-proof.js +64 -0
  41. package/lib/mcp/tools/agent/propose.js +29 -3
  42. package/lib/mcp/tools/agent/proxy.d.ts +3 -0
  43. package/lib/mcp/tools/agent/proxy.js +199 -0
  44. package/lib/mcp/tools/agent/publish.js +49 -77
  45. package/lib/mcp/tools/agent/session.d.ts +2 -1
  46. package/lib/mcp/tools/agent/session.js +26 -7
  47. package/lib/mcp/tools/agent/shared.js +17 -8
  48. package/lib/mcp/tools/authenticate/authenticate.d.ts +11 -0
  49. package/lib/mcp/tools/authenticate/authenticate.js +143 -121
  50. package/lib/mcp/tools/authenticate/builder-access.d.ts +8 -3
  51. package/lib/mcp/tools/authenticate/builder-access.js +8 -3
  52. package/lib/mcp/tools/mode/mode.d.ts +1 -1
  53. package/lib/mcp/tools/mode/mode.js +26 -7
  54. package/lib/old/client.d.ts +9 -0
  55. package/lib/old/client.js +19 -1
  56. package/lib/old/instructions.d.ts +9 -1
  57. package/lib/old/instructions.js +4 -6
  58. package/lib/old/login-cdp.d.ts +2 -2
  59. package/lib/old/login-cdp.js +46 -6
  60. package/lib/old/to-register.d.ts +20 -8
  61. package/lib/old/to-register.js +7 -1
  62. package/lib/old/tools/authenticate.js +9 -1
  63. package/lib/old/tools/get-provider-info.js +6 -5
  64. package/lib/old/tools/issue-app-credentials.js +3 -2
  65. package/lib/old/tools/me-providers.js +3 -1
  66. package/lib/old/tools/publish.d.ts +1 -1
  67. package/lib/old/tools/publish.js +132 -129
  68. package/lib/old/tools/update-provider-metadata.d.ts +1 -1
  69. package/lib/old/tools/update-provider-metadata.js +1 -1
  70. package/lib/old/tools/verify-sdk-setup.js +1 -1
  71. package/lib/proof/attestor.d.ts +3 -3
  72. package/lib/proof/debug-response-dump.js +4 -4
  73. package/lib/proof/index.d.ts +1 -0
  74. package/lib/proof/index.js +1 -0
  75. package/lib/proof/page-replay.d.ts +36 -0
  76. package/lib/proof/page-replay.js +76 -0
  77. package/lib/proof/pod-prover.d.ts +47 -0
  78. package/lib/proof/pod-prover.js +155 -0
  79. package/lib/proof/replay.d.ts +28 -0
  80. package/lib/proof/replay.js +39 -18
  81. package/lib/provider/dom-xpath.js +11 -9
  82. package/lib/provider/draft.d.ts +13 -8
  83. package/lib/provider/draft.js +47 -13
  84. package/lib/provider/injection-doc.d.ts +23 -1
  85. package/lib/provider/injection-doc.js +34 -21
  86. package/lib/provider/match-redaction-warning.d.ts +2 -2
  87. package/lib/provider/match-redaction-warning.js +3 -3
  88. package/lib/provider/matcher.js +122 -12
  89. package/lib/provider/secrets.d.ts +3 -3
  90. package/lib/provider/secrets.js +3 -3
  91. package/lib/provider/to-version.d.ts +12 -9
  92. package/lib/provider/to-version.js +16 -4
  93. package/lib/tools/registry.d.ts +5 -5
  94. package/lib/tools/registry.js +5 -5
  95. package/package.json +4 -3
  96. package/samples/demo-server/README.md +7 -6
  97. package/skill/SKILL.builder.md +356 -189
  98. package/skill/SKILL.common.md +314 -79
  99. package/skill/SKILL.old.md +340 -155
  100. package/skill/builder-mode.md +58 -17
  101. package/skill/hash-validation.md +170 -0
  102. package/skill/interception.md +235 -0
  103. package/skill/js-sdk-integration.md +13 -7
  104. package/skill/old-mode.md +53 -12
  105. package/skill/user-script.md +61 -0
  106. package/src/authoring/backend.ts +13 -10
  107. package/src/authoring/tools.ts +128 -9
  108. package/src/cdp/capture-engine.ts +8 -7
  109. package/src/cdp/client.ts +8 -0
  110. package/src/cdp/geo.ts +52 -0
  111. package/src/cdp/index.ts +1 -0
  112. package/src/consts.ts +1 -0
  113. package/src/live/cloudflared.ts +348 -0
  114. package/src/live/page.ts +145 -0
  115. package/src/live/tunnel.ts +157 -0
  116. package/src/live/viewer.ts +293 -0
  117. package/src/mcp/api/operations.gen.ts +34 -26
  118. package/src/mcp/api/to-zod.ts +1 -1
  119. package/src/mcp/api/tools.ts +1 -1
  120. package/src/mcp/instructions.ts +121 -48
  121. package/src/mcp/start.ts +4 -3
  122. package/src/mcp/tools/agent/analyze.ts +5 -1
  123. package/src/mcp/tools/agent/attach.ts +194 -22
  124. package/src/mcp/tools/agent/build.ts +26 -15
  125. package/src/mcp/tools/agent/capture.ts +5 -1
  126. package/src/mcp/tools/agent/eval.ts +4 -1
  127. package/src/mcp/tools/agent/find.ts +6 -5
  128. package/src/mcp/tools/agent/how-it-works.ts +62 -21
  129. package/src/mcp/tools/agent/live-view.ts +198 -0
  130. package/src/mcp/tools/agent/pod-proof.ts +90 -0
  131. package/src/mcp/tools/agent/propose.ts +31 -3
  132. package/src/mcp/tools/agent/publish.ts +54 -78
  133. package/src/mcp/tools/agent/session.ts +26 -5
  134. package/src/mcp/tools/agent/shared.ts +19 -8
  135. package/src/mcp/tools/authenticate/authenticate.ts +167 -165
  136. package/src/mcp/tools/authenticate/builder-access.ts +8 -3
  137. package/src/mcp/tools/mode/mode.ts +28 -7
  138. package/src/old/client.ts +30 -1
  139. package/src/old/instructions.ts +4 -10
  140. package/src/old/login-cdp.ts +50 -6
  141. package/src/old/to-register.ts +28 -8
  142. package/src/old/tools/authenticate.ts +9 -1
  143. package/src/old/tools/get-provider-info.ts +6 -5
  144. package/src/old/tools/issue-app-credentials.ts +3 -2
  145. package/src/old/tools/me-providers.ts +3 -1
  146. package/src/old/tools/publish.ts +140 -130
  147. package/src/old/tools/update-provider-metadata.ts +1 -1
  148. package/src/old/tools/verify-sdk-setup.ts +1 -1
  149. package/src/proof/attestor.ts +3 -3
  150. package/src/proof/debug-response-dump.ts +4 -4
  151. package/src/proof/index.ts +8 -0
  152. package/src/proof/page-replay.ts +131 -0
  153. package/src/proof/pod-prover.ts +231 -0
  154. package/src/proof/replay.ts +60 -19
  155. package/src/provider/dom-xpath.ts +11 -9
  156. package/src/provider/draft.ts +56 -13
  157. package/src/provider/injection-doc.ts +37 -24
  158. package/src/provider/match-redaction-warning.ts +3 -3
  159. package/src/provider/matcher.ts +151 -10
  160. package/src/provider/secrets.ts +3 -3
  161. package/src/provider/to-version.ts +30 -13
  162. package/src/tools/registry.ts +5 -5
  163. package/skill/injection-api.md +0 -335
@@ -1,17 +1,58 @@
1
- This server targets the Reclaim BUILDER backend. It exposes three tool layers: generated API tools (one per builder operation, snake_case), credential/verification helpers, and browser agent tools (CDP capture, synthesis, replay, proof).
2
-
3
- **Authentication:** try `get_me` first. On 401, `authenticate({})` opens a device-pairing session — give the user the `attachUrl`, then poll `authenticate({ code })` until LINKED. Tokens are cached ~2 days.
4
-
5
- **Provider-authoring flow:** `attach_browser` (local, Builder remote, or custom CDP) → `start_capture(tabId)` ⇒ a **`captureId`** (the session handle every browser/inspect tool takes as its first arg) → drive the page (`navigate`, `wait_for_page`, `eval_in_page`, `get_cookies`) → inspect (`list_requests`, `get_request`, `find_requests_containing`) → `propose_provider` ⇒ a **`draftId`** (the recipe + captured secrets held server-side; secrets never enter the transcript) → `replay_request(captureId, draftId)` → `run_proof(captureId, draftId, ownerAddress)` → resolve a `providerId` (**Reuse before create**, below) → `create_provider_version_from_capture` (lands on the draft branch) → `publish_provider_version` (PRIVATE) or `submit_provider_version_for_review` (PUBLIC).
6
-
7
- **Reuse before create (avoid duplicates):** `create_provider_version_from_capture` requires a `providerId`. Before minting a new one with `create_provider`, search your existing providers with `providers` (by domain / data point) — a site you've already built will be there. If one matches, pass its `providerId` so the capture lands as a NEW VERSION on that provider. Only `create_provider` (then use its returned id) when none exists. Re-running the flow against `create_provider` each time is what stacks duplicate providers in "My providers".
8
-
9
- **Multiple requests & login-gated data:** one version can carry many requests — `propose_provider` + `run_proof` each target request individually, then publish them together via `create_provider_version_from_capture({ providers: [draftA, draftB, …] })` (every entry is required at verification; the single-`provider` form still works for the one-request case). For data gated behind login/navigation, supply `jsUserScripts` — plain page JavaScript if needed that drives login/waits for user to login/waits for user to do multifactor auth/navigation with standard browser APIs (`window.location`, clicking elements, submitting forms) so the target request fires (page requests are intercepted automatically; `window.Reclaim` is only for rare edge cases) — and/or `allowedJsRequests` (extra requests the script may fire, e.g. pagination). NOTE: the builder verification client installs `jsUserScripts` to run before every page load (before navigating to `initialUrl`), matching the old-devtool `customInjection`; `window.Reclaim` itself is not yet exposed there, so prefer plain browser JS.
10
-
11
- **Templating:** bare `{{name}}` placeholders are provider params (values extracted from the response by the attestor). Values the CONSUMER must supply at verification time (e.g. a username in the URL path) are templated `{{context.<name>}}` and declared on the version as `requiredContext` — a JSON Schema set via `update_provider_version` while the version is on draft. The Builder validates every session-create's `context` against it (400 on mismatch; extra context fields are always allowed). `requiredContext` is optional — skip it when the recipe has no `{{context.*}}` placeholders. At verification time, requests are matched by exact origin+path: `{{context.<name>}}` segments match the consumer-sent value only, bare `{{name}}` values are extracted from the live traffic (plain-text alignment — works for JSON, XML, HTML, form-encoded, any text). Extract selectors apply client-side in spec order xPath → jsonPath → regex, each to the prior selection, so only the first selector constrains the full response; responses that cannot satisfy it (or miss a required match) are skipped. `propose_provider` returns advisory `verificationWarnings` for author-only decisions — surface them to the dev before publishing. **Two hard limitations:** (1) never `hash`/OPRF a target whose value also appears in the request URL — URL param values are always signed into context in the clear, so `propose_provider` throws rather than shipping a draft that can't verify; (2) ANY param — bare `{{SECRET_x}}` or consumer-supplied `{{context.SECRET_apiKey}}` alike — named with `SECRET` routes to `secretParams.paramValues` and CAN be used in the URL/geoLocation, but only up to a small TOTAL character budget summed across every secret value in that URL — 2 chars under `key-update` (default), 12 under `zk` — checked at verification time against the real value, since it doesn't exist yet at publish time. See `how_it_works` for the full reasoning.
12
-
13
- **Discover & resolve (find providers for the user's app):** when a dev wants to add verification but doesn't know which providers to use, run the same flow the Discover UI does. Ask which website their users prove data from, then `discover_data_points({ domain })` to list what's verifiable there. Confirm the data points with the dev, then `discover_resolve({ domain, dataPoints })` → the smallest provider set (`providers`, greedy combine), the `requiredContext` inputs to collect from the dev, and any `uncovered` points (author those with the provider-authoring flow above, then re-resolve). Create the session with `create_verification_session({ providers: [{ providerId }, …], context, verificationClientUrl })`, passing EVERY resolved provider id and the collected context. `merge.mergeable` only signals the set could collapse into one provider — you still pass them all; the Builder runs them in one session.
14
-
15
- **Verification sessions:** consumers authenticate with a per-org token (`rorg_…`, issued in the dashboard via `POST /orgs/{orgId}/token`) and create sessions via `POST /verifications/sessions`. Results are ECIES-encrypted (secp256k1 + AES-256-GCM) to the org's eth public key when its `canEncryptResult` is enabled — set via `PUT /orgs/{orgId}/keypair` with an eth public key — and delivered to the org's callback subscriptions (plaintext otherwise). Use the org/session tools (`get_verification_session`, `list_verification_events`, `list_org_verification_sessions`) to troubleshoot.
16
-
17
- **Backend mode:** this server can alternatively run against the devtools backend with a reduced tool set (no generated API tools, no orgs/review flows). `get_devtools_mode` reports the active mode and why it's in effect; `set_devtools_mode` persists a switch to `~/.reclaim/config.json` — it takes effect on the NEXT server start (restart/reconnect), and an explicit `USE_OLD_DEVTOOLS` env var overrides the persisted choice.
1
+ Reclaim provider-authoring tools, running against the BUILDER backend. Three
2
+ tool layers: generated API tools (one per builder operation, snake_case),
3
+ credential/verification helpers, and browser agent tools (CDP capture,
4
+ synthesis, replay, proof).
5
+
6
+ **A `provider` is a recipe for extracting a data point from a site. A
7
+ `verification` is an end-user session that proves a data point using one. Never
8
+ call a provider a "verification."**
9
+
10
+ **Read the guide before authoring.** `how_it_works({ topic })` serves it, one
11
+ topic at a time — call `how_it_works({})` for the topic index. Topics:
12
+ `overview`, `auth`, `browser`, `capture`, `draft`, `params`, `prove`, `publish`,
13
+ `discover`, `user-script`, `interception`, `hash-validation`, `credentials`,
14
+ `never-do`, `troubleshooting`.
15
+
16
+ **Authentication:** try `get_me` first. On 401, `authenticate({})` returns a
17
+ device-pairing `code` + `attachUrl` — give the user the URL, then poll
18
+ `authenticate({ code })` until LINKED. Tokens cache ~2 days.
19
+ (→ `how_it_works({ topic: "auth" })`)
20
+
21
+ **Browser choice is the developer's, and so is watching it.** `attach_browser`
22
+ advertises all four sources in its own description — relay them, including each
23
+ one's live-view line. A `builder` browser returns a hosted `liveViewUrl`; a
24
+ local or custom CDP browser is private until you call `share_browser_view`,
25
+ which tunnels a public URL with cloudflared. Say that option exists: nobody
26
+ guesses it.
27
+
28
+ **The flow:** `attach_browser` (ask the developer which browser source — local
29
+ Chrome, a CDP URL they supply, or a remote Builder browser) → `start_capture`
30
+ ⇒ **`captureId`** (the handle every browser/inspect tool takes first) → drive
31
+ the page (`navigate`, `wait_for_page`, `eval_in_page`, `get_cookies`) → inspect
32
+ (`list_requests`, `get_request`, `find_requests_containing`) →
33
+ `propose_provider` ⇒ **`draftId`** (recipe + captured secrets, held server-side
34
+ — secrets never enter the transcript) → `replay_request(captureId, draftId)` →
35
+ `run_proof(captureId, draftId, ownerAddress)` → resolve a `providerId` →
36
+ `create_provider_version_from_capture` (lands on the draft branch) →
37
+ `publish_provider_version` (PRIVATE) or `submit_provider_version_for_review`
38
+ (PUBLIC).
39
+
40
+ **Reuse before create.** `create_provider_version_from_capture` needs a
41
+ `providerId`. Search your existing providers with `providers` (by domain / data
42
+ point) BEFORE calling `create_provider` — routing every run through
43
+ `create_provider` is what stacks duplicates in "My providers".
44
+
45
+ **Never prove a public endpoint.** After `replay_request` succeeds, re-run it
46
+ with `withoutSecrets: true`. If the value still extracts, the endpoint is public
47
+ and the proof binds to nobody — find an auth-bound request instead.
48
+
49
+ **Two hard constraints when drafting** (details in topics `draft` / `params`):
50
+ never `hash`/OPRF a value that also appears in the request URL; any param named
51
+ with `SECRET` may be used in a URL only within a tiny TOTAL character budget (2
52
+ under `key-update`, 12 under `zk`), checked at verification time.
53
+
54
+ **Backend mode:** this server can instead run against the devtools backend with
55
+ a reduced tool set. `get_devtools_mode` reports the active mode and why;
56
+ `set_devtools_mode` persists a switch to `~/.reclaim/config.json`, effective on
57
+ the NEXT server start. An explicit `USE_OLD_DEVTOOLS` env var wins over the
58
+ persisted choice.
@@ -0,0 +1,170 @@
1
+ # Spec hashing: `bodySniff` and `verifyProof({ providerId })`
2
+
3
+ ## Contents
4
+
5
+ - The one fact everything follows from
6
+ - `bodySniff` — when to enable it, how to template a partly dynamic body
7
+ - How `verifyProof(proof, { providerId })` gates a proof
8
+ - Why `url` is hashed verbatim, and when that makes
9
+ `allowedJsRequests`/`allowedInjectedRequestData` unusable
10
+ - Optional fields, and the `dangerouslyDisableContentValidation` fallback
11
+ - Related topics
12
+
13
+ Everything here follows from one fact, confirmed directly from
14
+ `hashProviderParams` in `@reclaimprotocol/attestor-core`:
15
+
16
+ > The claim/spec hash is
17
+ > `keccak256(canonicalStringify({ url, method, body, responseMatches, responseRedactions }))`,
18
+ > and attestor-core does **no** template resolution, substitution, or
19
+ > `{{}}`-stripping on any of it. It hashes exactly the strings it is given.
20
+
21
+ ## `bodySniff`
22
+
23
+ A per-request field (it sits next to `url`/`method` on each `requestData` entry,
24
+ not a version-level setting): `{ enabled: boolean, template: string }`.
25
+
26
+ **`bodySniff.enabled` should be `true` whenever the request HAS a body at all**
27
+ — it isn't reserved for dynamic bodies. Only leave it off when the request
28
+ genuinely sends no body (typically a GET). Both publish tools in this package
29
+ already set `enabled`/`template` from `provider.body` for a normal
30
+ capture-and-replay provider, so you don't touch it there.
31
+
32
+ The rest of this section matters when authoring a claim through live
33
+ interception (`window.Reclaim.requestClaim`) whose body isn't the one literal
34
+ string you captured once — usually because a SMALL part is dynamic per
35
+ user/run.
36
+
37
+ `bodySniff` as a concept doesn't exist inside attestor-core at all: it's
38
+ translated one layer up (old-devtools' `providerUtils.ts`:
39
+ `body: bodySniff.enabled ? bodySniff.template : ''`), and THAT literal string —
40
+ including any unresolved `{{paramName}}` text still in it — is what reaches
41
+ `hashProviderParams`.
42
+
43
+ **The consequence, and why this differs from `templateParams`.** A live claim's
44
+ `body` can also be set to the literal template string containing
45
+ `{{paramName}}` rather than the substituted content, with the real value
46
+ supplied separately as a witness param. Since the body is hashed opaquely, a
47
+ claim whose `body` is template `T` and a spec whose `bodySniff.template` is that
48
+ same `T` hash identically, whatever the placeholder stood for. This is the
49
+ OPPOSITE of `templateParams`/`${var}` in `responseMatches`/`responseRedactions`,
50
+ which — per the JS SDK's `generateSpecsFromRequestSpecTemplate` — genuinely IS
51
+ substituted before hashing. Don't conflate them: `${var}` in a match/redaction
52
+ is resolved before hashing; `{{var}}` in a body is never resolved, on either
53
+ side.
54
+
55
+ **Practical use.** Work out exactly which part of the body varies across
56
+ users/runs — usually one small field, not the whole payload. Template ONLY that
57
+ part; leave everything else literal. For
58
+ `{"request":1,"type":"user_info","id":"12345"}` where only `id` varies, the
59
+ template is `{"request":1,"type":"user_info","id":"{{REQ_BODY_USER_ID}}"}` —
60
+ NOT `{{whole_body}}`, which throws away the ability to pin down everything else.
61
+ Then:
62
+
63
+ 1. In the claim you build for `requestClaim`, set
64
+ `bodySniff: { enabled: true, template: '<that exact string>' }` and add a
65
+ matching witness/extracted param `REQ_BODY_USER_ID: <the real id>`, so the
66
+ real value stays independently verifiable while staying out of the hash.
67
+ 2. In the corresponding `allowedInjectedRequestData` / `allowedJsRequests`
68
+ entry, set `body` to that EXACT SAME literal template, byte for byte — not a
69
+ `${var}` substitution.
70
+ 3. Check by hand that
71
+ `template.replace('{{REQ_BODY_USER_ID}}', '<a real sample id>')` reproduces
72
+ the real request body byte for byte. Extra whitespace, different key order,
73
+ or a field that also varies but wasn't templated → the hashes won't match.
74
+ Multiple varying fields → one `{{placeholder}}` and one witness param each;
75
+ you're not limited to one hole.
76
+
77
+ **Fallback — whole-body placeholder.** If precise per-field templating keeps
78
+ mismatching and you can't pin every variable field down, template the ENTIRE
79
+ body as `{{REQ_BODY_GRD}}`. The `GRD` suffix matters: per the template engine
80
+ (`convertTemplateToRegex`), a placeholder name ending in `GRD` becomes a GREEDY
81
+ match (`(.*)`, including embedded delimiters) instead of the default non-greedy
82
+ `(.*?)`, which is what you want for a placeholder covering the whole remaining
83
+ body. Strictly a fallback — nothing about the body's structure is pinned by the
84
+ hash anymore (any body at all hashes the same).
85
+
86
+ ## How `verifyProof(proof, { providerId })` gates
87
+
88
+ Traced from `@reclaimprotocol/js-sdk`
89
+ (`src/utils/proofValidationUtils.ts`, `src/utils/providerUtils.ts`,
90
+ `src/witness.ts`):
91
+
92
+ 1. `assertValidateProof` → `fetchProviderHashRequirementsBy(providerId, …)`
93
+ fetches the provider's CURRENT PUBLISHED CONFIG and builds a list of expected
94
+ hashes — one per `requestData` entry PLUS one per
95
+ `allowedInjectedRequestData` (old-devtools) / `allowedJsRequests` (builder)
96
+ entry.
97
+ 2. Each expected hash is `hashRequestSpec` → `hashProofClaimParams({ url,
98
+ method, body, responseMatches, responseRedactions })` — the same opaque
99
+ hashing as above, applied to the whole spec.
100
+ 3. The real submitted proof's `claimData.parameters` is hashed the same way,
101
+ with `isOptional`-driven 2^N combinatorial expansion over
102
+ `responseMatches`/`responseRedactions` PAIRS
103
+ (`getProviderParamsAsCanonicalizedString`) so an optional field can be
104
+ present or absent. **That expansion only touches match/redaction pairs, never
105
+ `url`.**
106
+ 4. `assertValidProofsByHash` requires the real proof's hash to equal exactly ONE
107
+ expected hash. No match → `verifyProof` rejects, even for a perfectly valid,
108
+ correctly signed proof whose `responseMatches` are 100% right.
109
+
110
+ ### `url` is hashed VERBATIM — zero template/regex resolution
111
+
112
+ Even with `urlType: REGEX` or `TEMPLATE`. Confirmed from
113
+ `generateSpecsFromRequestSpecTemplate` (the function that DOES resolve `${var}`
114
+ in matches/redactions): it spreads every other field through unchanged and never
115
+ touches `url`. `urlType` is consumed by a different validation path entirely
116
+ (matching a live request against the spec at capture time), never by this hash.
117
+
118
+ **So, before populating `allowedInjectedRequestData`/`allowedJsRequests` for a
119
+ `verifyProof({ providerId })` use case:** if the real captured URL contains ANY
120
+ component that varies between two otherwise-identical sessions — a tracking
121
+ nonce, a CSRF token, a cache-busting timestamp, a per-request trace id — the
122
+ hash from your declared spec can NEVER equal any real future proof's, no matter
123
+ how correct everything else is. This is common on JS-heavy sites (Slack
124
+ regenerates `_x_id`, `_x_csid` and `fp` on every API call, so a captured
125
+ `users.profile.getSections` URL is never identical twice, even seconds apart).
126
+
127
+ Populating the field for such a provider is **actively worse than leaving it
128
+ empty**: it converts "no provider-hash check was attempted" into
129
+ "`verifyProof({ providerId })` rejects every real proof, unconditionally."
130
+
131
+ **Verify first:** capture the same endpoint twice in two separate sessions
132
+ (ideally two users, or the same user on two days) and diff the full URL byte for
133
+ byte.
134
+
135
+ - Identical every time → safe to populate (`urlType: CONSTANT`, literal URL).
136
+ - Not identical → do not populate it expecting it to gate `verifyProof`. No
137
+ amount of care with `responseMatches`/`responseRedactions`/`body` fixes it on
138
+ either backend; it's a property of the hashing itself.
139
+
140
+ ### Optional fields
141
+
142
+ Independent of the `url` constraint: even with a stable URL,
143
+ `responseMatches`/`responseRedactions` must match EXACTLY, not just in spirit. A
144
+ provider whose extracted field SET varies per user (optional profile fields
145
+ present for some, absent for others) needs every such field marked
146
+ `isOptional: true` on BOTH the match and its paired redaction, so the 2^N
147
+ expansion produces a valid hash for the "absent" case. A sometimes-present field
148
+ that isn't marked optional breaks the hash for every user missing it — the same
149
+ failure mode as the `url` constraint, scoped to one field.
150
+
151
+ ### Fallback
152
+
153
+ Whether it's an unstable `url`, an unmarked optional field, or any other
154
+ persistent mismatch, the way out is on the VERIFYING side:
155
+ `verifyProof(proof, { dangerouslyDisableContentValidation: true })`. That skips
156
+ steps 1–4 entirely and trusts only the attestor's cryptographic signature; it
157
+ does NOT check that the proof's url/method/body/matches/redactions match
158
+ anything you declared. A caller-supplied `{ hashes: [...] }` computed some other
159
+ way is the alternative when you still want hash-equality enforced, just not
160
+ derived from the published config. Reach for
161
+ `dangerouslyDisableContentValidation` deliberately, never as a silent default —
162
+ it trades away the guarantee that the proof is shaped the way the provider's
163
+ spec says.
164
+
165
+ ## Related topics
166
+
167
+ - `user-script` — `allowedJsRequests` / `allowedInjectedRequestData`, the fields
168
+ this page's `url` constraint applies to.
169
+ - `prove` — `run_proof` and diagnosing a proof that fails before it ever reaches
170
+ `verifyProof`.
@@ -0,0 +1,235 @@
1
+ # Live interception: `injectionType` / `interceptorType`
2
+
3
+ ## Contents
4
+
5
+ - The field, per backend
6
+ - Which mechanism to pick, and when to use any of them at all
7
+ - When a site won't load, or won't let the user sign in — ask which clients
8
+ ship it, bisect, then the remedies
9
+ - The document-replay flag, and why the two backends invert its polarity
10
+ - Recognizing live interception on a provider you didn't author
11
+ - Related topics
12
+
13
+ Separate from the user script's CONTENT is HOW the client captures network
14
+ traffic for it to consume. This is a per-version field alongside
15
+ `customInjection`/`jsUserScripts`, not something you write into the script.
16
+
17
+ Whichever mechanism is selected, the script consumes what was caught through the
18
+ SAME API: `window.reclaimInterceptor.addResponseMiddleware(middleware,
19
+ middlewareName?)`. That fires for any intercepted request under EVERY mechanism
20
+ below, `CDP` included — it is not HAWKEYE-specific.
21
+
22
+ ## The field, per backend
23
+
24
+ - **old-devtools:** `injectionType`, one of
25
+ `NONE | MSWJS | XHOOK | CDP | HAWKEYE`. The backend's own column default is
26
+ **`HAWKEYE`, not `NONE`** — harmless for the standard flow, since with no
27
+ interceptor script installed nothing calls `addResponseMiddleware`.
28
+ `create_provider_version_from_capture` exposes it as an author-settable input
29
+ with NO forced default: omit it and the backend default applies; pass an
30
+ explicit value (including `NONE`) for certainty. When passed it's sent on both
31
+ register and add-version, and the tool's always-on add-version follow-up (see
32
+ `disableRequestReplay` below) reapplies it, so an explicit value is guaranteed
33
+ correct by the time that version lands.
34
+ - **builder:** `webSettings.inapp.interceptorOptions.interceptorType`, one of
35
+ `HAWKEYE | MSWJS | CDP`. There is no `NONE` here — **omitting
36
+ `interceptorOptions` entirely IS builder's no-interception signal**, and the
37
+ publish tool omits `inapp` rather than defaulting to `HAWKEYE`.
38
+
39
+ Don't assume a non-`NONE` `injectionType` on an existing provider means
40
+ something is broken — HAWKEYE-by-default is normal and inert for the standard
41
+ flow.
42
+
43
+ ## Which mechanism, and when to use any of them at all
44
+
45
+ - **`CDP`** — interception at the browser/DevTools-protocol level, below the
46
+ page's own JS. Works uniformly for every request the page makes, including its
47
+ own document/navigation load. No caveats; works best.
48
+ - **`HAWKEYE` / `MSWJS` / `XHOOK`** — interception inside the page's JS context
49
+ (different libraries, same idea). Fine for ordinary sub-resource/API requests,
50
+ but the page's OWN document request is a special case — see
51
+ "The document-replay flag".
52
+ - **Reach for any of them only when the data genuinely can't be reproduced by
53
+ an independent replay** — for example, it depends on one-time server-side
54
+ state (a nonce, a websocket push) that asking again wouldn't return. For
55
+ everything else the standard capture → replay flow is simpler, and is what
56
+ `propose_provider` / `run_proof` are built for.
57
+
58
+ ## When a site won't load, or won't let the user sign in
59
+
60
+ Three symptoms point the same way:
61
+
62
+ - the page loads blank, half-rendered, or spins forever;
63
+ - sign-in is refused, or the form submits and bounces straight back;
64
+ - login succeeds, then the next page breaks.
65
+
66
+ ### First, ask which clients ship this provider
67
+
68
+ **Ask the author which verification clients their END USERS will run — don't
69
+ infer it.** It decides whether any of this section applies, and it is not
70
+ something the session can tell you: the client you author through is `builder`,
71
+ whereas production is whatever the author's own app integrates. A provider that
72
+ looks healthy here can fail for every real user, and the reverse.
73
+
74
+ Ask plainly, and offer the list: *"Which clients will run this in production —
75
+ the InApp SDK, the verifier app, the browser extension, portals, or something
76
+ else? I need it to tell whether interception is a plausible cause."* If they
77
+ ship more than one, the diagnosis has to hold for all of them, so treat the
78
+ strictest row below as the answer.
79
+
80
+ | Client the end user runs | Where it intercepts | Does this section apply? |
81
+ | --- | --- | --- |
82
+ | `inapp-sdk` | Inside the page's JS | **Yes** |
83
+ | `verifier-app` | Inside the page's JS | **Yes** |
84
+ | `reclaim-browser-extension` | Inside the page's JS | **Yes** |
85
+ | `portals` | Browser level, over CDP | No |
86
+ | `builder` | Server-side CDP, out of band | No |
87
+
88
+ `builder` is the row for authoring and for a session created with no
89
+ `verificationClientUrl`. Reaching it means you are diagnosing the authoring
90
+ tool, not what the author's users will hit — which is exactly why you ask
91
+ first.
92
+
93
+ **If production is only `portals` or `builder`, stop here and look elsewhere.**
94
+ CDP intercepts below the page's own JavaScript, so there is nothing for a site
95
+ to detect and nothing to disable — and `builder` never delivers intercepted
96
+ traffic to the page at all (topic `user-script`). A verification on those that
97
+ won't load or won't sign in is almost always a CAPTCHA or bot challenge
98
+ instead: check `analyze_request_constraints` for a `botChallenge` concern, and
99
+ treat it as a site that needs a human through the live view rather than a
100
+ provider to fix.
101
+
102
+ ### Then bisect, cheapest change first
103
+
104
+ **For the three in-page clients, suspect interception before the recipe.** A
105
+ JS-level interceptor patches the page's own `fetch`/`XMLHttpRequest`, and some
106
+ sites treat that as tampering: anti-bot vendors probe for it directly (a
107
+ patched `fetch.toString()` is a well-known signal), and a few break on their
108
+ own without meaning to. None of that shows up while you author — the MCP agent
109
+ captures over CDP, out of band, so the site behaves normally right up until a
110
+ real verification runs.
111
+
112
+ Try document replay first and interception second. Often it is only
113
+ DOCUMENT REPLAY that breaks a site, not interception as a whole — the page's
114
+ own top-level navigation is the one request pulled through the interceptor, and
115
+ that is the one some sites refuse to survive. Turning it off is a much smaller
116
+ change than going interceptor-free, and it tells you which half is at fault.
117
+ Copy this checklist and work down it:
118
+
119
+ - [ ] 1. **Turn document replay OFF, keep the interceptor.** Old-devtools:
120
+ `disableRequestReplay: true`. Builder:
121
+ `interceptorOptions.isDocumentRequestReplayEnabled: false` (the two
122
+ backends name this with opposite polarity — see "The document-replay
123
+ flag"). Both publish tools already default it off, so a provider that
124
+ breaks here usually had it switched ON deliberately.
125
+ - [ ] 2. **Retry the verification.** Reload, then sign in as a real end-user
126
+ would. Works now → stop here. This is the cheapest fix and you keep the
127
+ interceptor.
128
+ - [ ] 3. **Still broken? Turn interception off entirely.** Old-devtools:
129
+ publish with `injectionType: NONE`. Builder: omit `interceptorOptions`
130
+ entirely — there is no `NONE` there, and omitting it IS the off switch.
131
+ - [ ] 4. **Retry again, and compare.** Loads and signs in now → interception
132
+ itself was the cause; pick a remedy below. Still broken → interception
133
+ is innocent; go back to the recipe (topic `prove`) or the browser
134
+ (topic `browser`).
135
+
136
+ If step 2 fixed it, you are done — the interceptor stays and only the document
137
+ request stops being replayed. Note that under `CDP` the flag is inert, so a
138
+ `CDP` provider that breaks is never a document-replay problem.
139
+
140
+ ### Remedies
141
+
142
+ If it took step 3, take the first remedy that fits — in this order:
143
+
144
+ 1. **Try a different interceptor.** They patch the page in different ways, so
145
+ a site that rejects one often accepts another — work along the row for your
146
+ backend before giving up on interception:
147
+ - old-devtools: `HAWKEYE` → `MSWJS` → `XHOOK` → `CDP`
148
+ - builder: `HAWKEYE` → `MSWJS` → `CDP`
149
+
150
+ `CDP` is the one that cannot be detected — it intercepts below the page's
151
+ JavaScript, so nothing in the page is patched. Prefer it when the target
152
+ client supports it; the in-page clients above may not, which is why the
153
+ JS-level mechanisms are worth cycling through first.
154
+ 2. **Ship without it.** Most providers never needed an interceptor: the
155
+ standard capture-and-replay flow proves the data on its own. Leave it off
156
+ unless the value genuinely can't be reproduced by an independent replay
157
+ (see "Which mechanism, and when to use any of them at all").
158
+ 3. **Drive the claim yourself.** Where the data only exists in a live page,
159
+ skip `reclaimInterceptor` and have the user script navigate to the page
160
+ that shows the value, read what the page already holds, and call
161
+ `window.Reclaim.requestClaim` with it. Declare anything the script fires in
162
+ `allowedJsRequests` / `allowedInjectedRequestData`, and read topic
163
+ `hash-validation` first — a session-unique URL there makes
164
+ `verifyProof({ providerId })` reject every real proof.
165
+
166
+ ⚠️ **Remedy 3 needs a client that consumes `window.Reclaim`.** The InApp
167
+ SDK, the browser extension, and the portals do. Builder's own verification
168
+ client injects the bridge but never drains it, so `requestClaim` is inert
169
+ there — see topic `user-script`. Don't reach for remedy 3 on a Builder-run
170
+ verification.
171
+
172
+ ## The document-replay flag
173
+
174
+ **Both publish tools default this to disabled, and both let the author override
175
+ it.**
176
+
177
+ - **old-devtools:** `disableRequestReplay`, schema default `false`. The publish
178
+ tool sends `true` by default, auto-following a fresh register with an
179
+ add-version call to apply it (the register endpoint hardcodes it `false`
180
+ regardless of what's sent). Pass `disableRequestReplay: false` explicitly for
181
+ real document replay.
182
+ - **builder:** `webSettings.clientOptions.inapp.interceptorOptions.isDocumentRequestReplayEnabled`
183
+ — positive naming, schema default `false`. The publish tool sets `false`
184
+ whenever you pass `interceptorOptions` at all. Pass
185
+ `interceptorOptions.isDocumentRequestReplayEnabled: true` to enable it.
186
+
187
+ The two backends' polarity is inverted, so don't carry an assumption across.
188
+ The old dashboard makes this worse: its checkbox is labeled "Enable Document
189
+ Request Replay" and is CHECKED when `disableRequestReplay` is `false`. Its field
190
+ hint, verbatim: *"When enabled (default), the main document HTTP request is
191
+ actually sent to get the response. When disabled, the expected request and
192
+ response are constructed locally without making the network call."*
193
+
194
+ **This does NOT gate `addResponseMiddleware` in general** — that fires for any
195
+ intercepted request regardless. What it actually controls is whether the CURRENT
196
+ PAGE'S OWN document/navigation request (the one that loads the page's HTML, not
197
+ a sub-resource or API call) is sent for real over the network, for the non-CDP
198
+ mechanisms. It only matters when the request you're proving *is* that document
199
+ request — that is, when the target value is embedded in the page's own HTML
200
+ rather than fetched by a separate API call.
201
+
202
+ - Document request genuinely sent → `addResponseMiddleware` sees it like any
203
+ other request. This is what you need to prove data straight out of the page's
204
+ HTML.
205
+ - Document request constructed locally → the interceptor never observes it
206
+ (every OTHER request is still intercepted normally). This is a **per-site
207
+ compatibility escape hatch**: some sites misbehave when their top-level
208
+ navigation is pulled through a JS-level interceptor. Not a privacy or
209
+ architecture choice.
210
+ - **`CDP` is exempt from all of it** — it intercepts at the browser level, so
211
+ the document request is never a special case and this flag is inert.
212
+
213
+ ## Recognizing this on a provider you didn't author
214
+
215
+ `get_provider_info` (old-devtools mode; reads the public SDK backend) returns
216
+ the raw `providerConfig`. If `injectionType`/
217
+ `interceptorType` is set to something other than the inert default AND the
218
+ `customInjection`/`jsUserScripts` calls `reclaimInterceptor.addResponseMiddleware`
219
+ (or references `MSWJS`/`XHook`), that provider is on the live-interception path,
220
+ not the standard replay flow — read the script before carrying it forward or
221
+ editing around it.
222
+
223
+ If, on top of that, the request being proven IS the login/landing page itself
224
+ (its `url` matches `loginUrl`, so it's the document request) and the mechanism
225
+ isn't `CDP`, check the document-replay flag too: a value that routes it around
226
+ live interception was a deliberate site-compatibility decision, not an
227
+ oversight.
228
+
229
+ ## Related topics
230
+
231
+ - `user-script` — what the script itself does with intercepted traffic, and why
232
+ `window.Reclaim` is inert on Builder's own client.
233
+ - `hash-validation` — declaring script-fired requests without breaking
234
+ `verifyProof({ providerId })`.
235
+ - `browser` — live view, for handing a stuck sign-in to a human.
@@ -3,10 +3,16 @@
3
3
  Keep `appSecret` on your BACKEND only. Everything below runs server-side;
4
4
  the browser only ever sees the `requestUrl`.
5
5
 
6
+ ## Contents
7
+
8
+ - Testing locally, without deploying anything
9
+ - Steps 1-4: install, create the proof request, open it, verify the callback
10
+ - Gotchas
11
+
6
12
  ## Testing locally, without deploying anything
7
13
 
8
14
  - **providerId `"example"`**: a canonical built-in smoke-test provider —
9
- no need to author/publish your own provider just to confirm the SDK
15
+ no need to author and publish your own provider to confirm the SDK
10
16
  plumbing works end-to-end. Swap in your real providerId once this passes.
11
17
  - **A public callback URL**: `setAppCallbackUrl` needs a URL Reclaim's
12
18
  servers can reach over the public internet — `localhost` will never
@@ -15,9 +21,9 @@ the browser only ever sees the `requestUrl`.
15
21
  alongside your local server, then set `PUBLIC_URL` to the
16
22
  `https://<id>.ngrok-free.app` URL it prints. Zero-deploy, but the tunnel
17
23
  URL is temporary.
18
- - **A dev/staging server you can deploy to quickly**: if you already have
19
- one reachable from the internet, just point `PUBLIC_URL` at it instead —
20
- skips the extra local tool.
24
+ - **A development or staging server you can deploy to quickly**: if you
25
+ already have one reachable from the internet, point `PUBLIC_URL` at it
26
+ instead — that skips the extra local tool.
21
27
  Without either, only the frontend-side flow (opening `requestUrl` and
22
28
  reading the result client-side) works — the async webhook never lands.
23
29
  - `scaffold_reclaim_demo_server` defaults `providerId` to `"example"`; its
@@ -63,9 +69,9 @@ reclaimProofRequest.setContext(
63
69
  const requestUrl = await reclaimProofRequest.getRequestUrl();
64
70
 
65
71
  // Hand the requestUrl to the frontend to open in a new tab/window/webview.
66
- // If you need to rehydrate this request object later (e.g. in the webhook
67
- // handler, in a different process), persist reclaimProofRequest.toJsonString()
68
- // (sync) and rehydrate with:
72
+ // If you need to rehydrate this request object later (for example, in the
73
+ // webhook handler, in a different process), persist
74
+ // reclaimProofRequest.toJsonString() (sync) and rehydrate with:
69
75
  // const req = await ReclaimProofRequest.fromJsonString(json); // ASYNC
70
76
  ```
71
77
 
package/skill/old-mode.md CHANGED
@@ -1,19 +1,60 @@
1
- This server is running against the devtools backend, so its capabilities are intentionally limited.
1
+ Reclaim provider-authoring tools, running against the OLD DEVTOOLS backend —
2
+ intentionally reduced: no generated API tools, no organizations, no public
3
+ review flow. Only GET/POST requests can be captured into providers.
2
4
 
3
- **Provider-authoring flow:**
5
+ **A `provider` is a recipe for extracting a data point from a site. A
6
+ `verification` is an end-user session that proves a data point using one. Never
7
+ call a provider a "verification."**
4
8
 
5
- 1. `reclaim_authenticate` — by default opens the dashboard in Chrome, waits for you to sign in, captures the token automatically, and closes the tab (or paste a `token` / pass an eth `ethAddress` to skip the browser). Do this once before publishing or listing.
6
- 2. `attach_browser` → `start_capture` → drive the page (`navigate`, `wait_for_page`) → inspect (`list_requests`, `get_request`, `find_requests_containing`). Browser source is independent of this publishing mode: local Chrome works as before; `builder` uses `authenticate_builder` + `list_builder_organizations` to allocate a quota-accounted Popcorn browser and returns a shareable live-view URL; `custom` accepts a caller-supplied CDP websocket.
7
- 3. `propose_provider` to draft a provider, `replay_request` to pre-flight, `run_proof` to verify with the attestor. OPRF/`hash` redactions are optional and supported. When the data spans MULTIPLE requests (e.g. a landing call + a details call), propose + `run_proof` EACH request to get one proven draft per request.
8
- 4. `create_provider_version_from_capture` to publish. With NO `providerId` it REGISTERS A NEW provider (re-running creates a duplicate); pass a `providerId` from `get_me_providers` to add a new version to an existing provider instead. For a MULTI-REQUEST provider, pass `providers: [draftA, draftB, …]` (every proven draft) so EACH becomes a parallel `requestData` entry on the one version — passing a single `provider` saves only that one request.
9
- 5. `get_me_providers` lists the providers you own (the only provider-listing tool available in this mode).
9
+ **Read the guide before authoring.** `how_it_works({ topic })` serves it, one
10
+ topic at a time — call `how_it_works({})` for the topic index. Topics:
11
+ `overview`, `auth`, `browser`, `capture`, `draft`, `params`, `prove`, `publish`,
12
+ `user-script`, `interception`, `hash-validation`, `credentials`, `never-do`,
13
+ `troubleshooting`.
10
14
 
11
- **Editing / new versions (ownership):** you can only add a version to a provider **you own** — the `providerId` must come from `get_me_providers`. Trying to version a public provider you don't own fails with a permission error; that is expected. To publish your own version of such a site, re-run `create_provider_version_from_capture` **without** `providerId` to register your own copy (a separate provider you own). If versioning a provider you *do* own fails, confirm you authenticated as the account that created it.
15
+ **Browser choice is the developer's, and so is watching it.** `attach_browser`
16
+ advertises all four sources in its own description — relay them, including each
17
+ one's live-view line. A `builder` browser returns a hosted `liveViewUrl`; a
18
+ local or custom CDP browser is private until you call `share_browser_view`,
19
+ which tunnels a public URL with cloudflared. Say that option exists: nobody
20
+ guesses it.
12
21
 
13
- Only GET/POST endpoints can be captured into providers here. There is no Builder organization-management, credential-linking, or public-review workflow in this mode; the narrow `authenticate_builder` and `list_builder_organizations` exception exists only to allocate Builder-hosted authoring browsers.
22
+ **The flow:** `reclaim_authenticate` once → `attach_browser` (ask the developer
23
+ which browser source — local Chrome, a CDP URL they supply, or a remote Builder
24
+ browser) → `start_capture` ⇒ **`captureId`** → drive the page (`navigate`,
25
+ `wait_for_page`, `eval_in_page`) → inspect (`list_requests`, `get_request`,
26
+ `find_requests_containing`) → `propose_provider` ⇒ **`draftId`** →
27
+ `replay_request(captureId, draftId)` → `run_proof(captureId, draftId,
28
+ ownerAddress)` → `create_provider_version_from_capture`.
14
29
 
15
- **Diagnosing a session:** `session_analytics_logs` fetches the analytics logs for a verification session by id (from the logs service) — use it to see what happened in a session (matches, claims, failures).
30
+ **Publishing and ownership:** with NO `providerId`, publish REGISTERS A NEW
31
+ provider — re-running creates a duplicate. Pass a `providerId` from
32
+ `get_me_providers` (the only provider-listing tool here) to add a version to an
33
+ existing provider instead; you can only version a provider you own. For a
34
+ multi-request provider pass `providers: [draftA, draftB, …]` so each becomes a
35
+ parallel `requestData` entry on the one version.
16
36
 
17
- **Templating:** bare `{{name}}` placeholders are provider params (values extracted from the response). Values the CONSUMER must supply at verification time (e.g. a username in the URL path) use the `{{context_<name>}}` form — this backend does not support the dotted `{{context.<name>}}` interpolation the new builder uses.
37
+ **Two independent auth systems — don't confuse them.** `reclaim_authenticate`
38
+ authorizes everything in this mode; its Firebase token is short-lived (~1h), so
39
+ an auth error on publish or `get_me_providers` means re-run it.
40
+ `authenticate_builder` covers only two Builder-exclusive features: allocating a
41
+ remote browser (`attach_browser mode="builder"`) and
42
+ `list_builder_organizations`. It authorizes nothing else. `get_devtools_mode`
43
+ reports both.
18
44
 
19
- **Backend mode:** this is the reduced devtools surface — the BUILDER backend (generated API tools, orgs, verification sessions, public review) is one switch away. `get_devtools_mode` reports the active mode and why it's in effect; `set_devtools_mode` persists a switch to `~/.reclaim/config.json` — it takes effect on the NEXT server start (restart/reconnect), and an explicit `USE_OLD_DEVTOOLS` env var overrides the persisted choice.
45
+ **Never prove a public endpoint.** After `replay_request` succeeds, re-run it
46
+ with `withoutSecrets: true`. If the value still extracts, the endpoint is public
47
+ and the proof binds to nobody — find an auth-bound request instead.
48
+
49
+ **Templating:** bare `{{name}}` placeholders are extracted from the response.
50
+ Values the CONSUMER supplies at verification time use `{{context_<name>}}` —
51
+ this backend does NOT support the builder's dotted `{{context.<name>}}` form.
52
+
53
+ **Diagnosing a session:** `session_analytics_logs` fetches a verification
54
+ session's analytics logs by id (matches, claims, failures).
55
+
56
+ **Backend mode:** the BUILDER backend (generated API tools, orgs, verification
57
+ sessions, public review) is one switch away. `get_devtools_mode` reports the
58
+ active mode and why; `set_devtools_mode` persists a switch to
59
+ `~/.reclaim/config.json`, effective on the NEXT server start. An explicit
60
+ `USE_OLD_DEVTOOLS` env var wins over the persisted choice.