@tinkerlandai/indiana-sdk 0.5.0 → 0.6.0

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 (47) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +177 -8
  3. package/README.zh-CN.md +94 -6
  4. package/dist/{ai-usage-BzydP8eN.d.ts → ai-usage-B4W5m4Ie.d.ts} +21 -4
  5. package/dist/chunk-4JBIKJMU.js +4 -0
  6. package/dist/chunk-5TEZEOHX.js +1 -0
  7. package/dist/chunk-5WY3AXSS.js +1 -0
  8. package/dist/chunk-HU2HIDVT.js +11 -0
  9. package/dist/chunk-OHDY27EI.js +1 -0
  10. package/dist/chunk-OK2Q6IJD.js +1 -0
  11. package/dist/chunk-PCVQHHEE.js +1 -0
  12. package/dist/chunk-RMJJOSVZ.js +1 -0
  13. package/dist/chunk-S2MSIA57.js +17 -0
  14. package/dist/chunk-S5TVMQRC.js +1 -0
  15. package/dist/configuration-sync-Bnu8s4pc.d.ts +1314 -0
  16. package/dist/{customer-page-aZb5Twue.d.ts → customer-page-B4FGCVpt.d.ts} +3 -0
  17. package/dist/funnel-runtime-host-L3WILUHK.js +1 -0
  18. package/dist/funnel-runtime-start-Zbd5ro5r.d.ts +30920 -0
  19. package/dist/{growth-rules-D64pEvg9.d.ts → growth-rules-B4YYy64R.d.ts} +8 -1
  20. package/dist/index.d.ts +14950 -5193
  21. package/dist/index.js +1 -1
  22. package/dist/native-shell.js +1 -1
  23. package/dist/openrouter.d.ts +1 -1
  24. package/dist/openrouter.js +1 -1
  25. package/dist/page.d.ts +6 -8
  26. package/dist/page.js +1 -1
  27. package/dist/paywall-TCE5DE5T.js +1 -0
  28. package/dist/popup.d.ts +5 -2
  29. package/dist/popup.js +6 -6
  30. package/dist/server.d.ts +4 -3
  31. package/dist/server.js +1 -1
  32. package/dist/src-VGIVVM27.js +1 -0
  33. package/dist/{visual-editor-agent-FDCV3BXP.js → visual-editor-agent-WW65CMKW.js} +1 -1
  34. package/dist/visual.d.ts +4 -2
  35. package/dist/visual.js +1 -1
  36. package/package.json +1 -1
  37. package/dist/chunk-3OSMEG44.js +0 -17
  38. package/dist/chunk-GA6X57BO.js +0 -1
  39. package/dist/chunk-GTBD4BJ4.js +0 -11
  40. package/dist/chunk-QUW4UOKZ.js +0 -4
  41. package/dist/chunk-RMFH5TWV.js +0 -1
  42. package/dist/chunk-YNUVRZRQ.js +0 -1
  43. package/dist/funnel-runtime-host-RZRB2CY6.js +0 -1
  44. package/dist/funnel-runtime-start-JHA9ES24.d.ts +0 -9199
  45. package/dist/paywall-MYBEOVTQ.js +0 -1
  46. package/dist/src-E2GXK6WA.js +0 -1
  47. /package/dist/{chunk-ZT523F45.js → chunk-BIFEEVQO.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -2,7 +2,28 @@
2
2
 
3
3
  Release facts and artifact evidence: [SDK release ledger](../../docs/sdk-releases.md). Earlier personal-package versions are preserved in [@houzhen888/groove-grow history](../../docs/sdk-changelog-groove-grow.md). The two packages have independent version histories.
4
4
 
5
- ## Unreleased
5
+ ## 0.6.0 — 2026-10-21
6
+
7
+ - Default SDK API requests to `https://api.tinker.cash` while retaining explicit `baseUrl` overrides. Public configuration synchronization uses the same default through its optional `origin`; page/visual clients follow the same entry. Test apps explicitly use `https://api.dev.tinker.cash`. Native Swift/Kotlin source follows the default, with separate package releases.
8
+ - Publish on `beta`, retaining `latest = 0.5.0`. Package publication does not deploy runtime/database changes or certify Go business readiness; the latest API entry checks returned HTTP 503 for business readiness.
9
+
10
+ - Use protocol 0.6 and current-page JSON rendering. Local and Remote use one canonical Run, native command/action receipts, business executor and Queue recovery.
11
+ - Add `Customer.forFunnel(...)` to bind exact Funnel/version context for Coupon, Offering, Paywall and attach-quote requests. Independent use retains capability-owned rules.
12
+ - Preserve exact purchase-command restoration; retire optional client observations and cross-capability binding switches. Publication and hosted environment facts are recorded separately in the release ledger.
13
+
14
+ - Adopt Funnel 4 / graph 5 unified variable registration: source-constrained input/node writes, explicit imports, `inputs[].variable`, named `writes` and shared variable reads. Preserve existing answer/result storage, Back invalidation and independent commercial eligibility. Native contracts are regenerated; database acceptance remains separately scoped.
15
+
16
+ - Unify formal Local and Remote Funnels on the same server Run, business executor, command/action idempotency and Queue recovery. Local submits fenced native GB calculations; pending calculations release the worker. Retire the standalone host business executor and presentation-state owner. Preserve original Customer/Key proof, independent Coupon self-claim/grant authorization, exact retries and native wire generation. Old host checkpoints require explicit reconciliation.
17
+
18
+ - Use SDK 0.6.0 / protocol 0.6: Remote Funnel responses embed only the current page document. Local and Remote share JSON rendering; the host no longer downloads page-specific HTML. Preserve authenticated dynamic data, pause handling, accepted-command recovery and server-side execution. Regenerate native wire schemas and restrict native frame navigation to the shared same-origin renderer. Existing npm 0.5.0 keeps its original render-reference contract.
19
+
20
+ - Add opt-in `createConfigurationSync` for shared public Local GB/Funnel/page configuration, bounded initialization, immutable artifact verification and strict control freshness. Remote graphs are excluded, and explicit Indiana commands bypass CDN admission. Matching gateway is restricted to Indiana-test; no npm or production publication is implied.
21
+
22
+ - Add explicit server-side `createFunnelDecisionClient().local()` evaluation for pure Funnels, with scoped configuration reuse, host-persisted Sticky and opt-in session exposure deduplication. A dedicated `local_configuration` operation preserves existing configuration responses; remote `decide` and committed financial/Run receipts retain their authority. Requires matching server source; no package or runtime promotion is implied.
23
+
24
+ ## Distribution tags — 0.5.0 latest — 2026-10-19
25
+
26
+ - Point `latest` to the existing `0.5.0`; retain `beta = 0.5.0`. Default npm installation resolves 0.5.0, matching the separately deployed production protocol 0.5. Package bytes and runtime remain unchanged; no package was republished.
6
27
 
7
28
  ## 0.5.0 — 2026-10-19
8
29
 
package/README.md CHANGED
@@ -2,21 +2,21 @@
2
2
 
3
3
  [简体中文](./README.zh-CN.md)
4
4
 
5
- Version `0.5.0` uses protocol `0.5`. It adds billing-window `anchor` responses, standalone Invoice classification (`sourceKind: manual`), Invoice management methods and current subscription invoice-mode/anchor inputs. The anchor name, values and billing semantics come from pinned Autumn; the Invoice source classification is an Indiana adaptation.
5
+ Version **0.6.0 / protocol 0.6** requires a matching Indiana runtime and database. Remote Funnel responses contain the current page's `ScreenDocument` JSON; Local and Remote use the same renderer, canonical Run, business receipts and Queue recovery. Remote graphs and private rules stay on the server.
6
6
 
7
- Indiana's JavaScript and TypeScript SDK supports browsers, Node.js, screen rendering and optional AI usage integrations. This package requires a server and database implementing protocol `0.5`; publishing the SDK does not deploy them. For a server still advertising protocol `0.3`, use SDK `0.3.1`.
8
-
9
- Environment collection controls introduced in protocol `0.4` remain available. Disabled collection keeps existing history and does not change billable usage. SDKs cache the policy for 30 seconds; the database checks each batch. Older `0.x` protocols are rejected before business operations.
7
+ Pin this package to a matching protocol 0.6 service. The latest API entry checks found business readiness returning HTTP 503 on both production and test Go services; npm publication does not make those services ready. Actual publication, tags and environment state are recorded in the [release ledger](../../docs/sdk-releases.md).
10
8
 
11
9
  ```sh
12
- npm install --save-exact @tinkerlandai/indiana-sdk@0.5.0
10
+ npm install --save-exact @tinkerlandai/indiana-sdk@0.6.0
13
11
  ```
14
12
 
13
+ Indiana's JavaScript and TypeScript SDK supports browsers, Node.js, screen rendering and optional AI usage integrations. Environment collection controls and existing Invoice/billing-window contracts remain available. Older 0.x protocols are rejected before business execution.
14
+
15
15
  ## Version compatibility and support
16
16
 
17
17
  During `0.x`, only the current protocol is supported. Starting with `1.0`, compatibility is preserved within each stable major version. Once the next stable major is published, the preceding stable major remains supported for at least 12 calendar months, with at least 180 days' public notice before retirement. The later deadline applies. See the [support policy](https://tinker.cash/docs/release-notes#support-policy).
18
18
 
19
- Version `0.5.0` sends protocol and SDK version headers and validates the protocol declared by successful responses. Headerless historical clients retain the fixed identity `0.2`; they do not follow the current protocol automatically. Updating source code does not change an existing npm artifact or deploy a server.
19
+ Version `0.6.0` sends protocol and SDK version headers and validates the protocol declared by successful responses. Headerless historical clients retain the fixed identity `0.2`; they do not follow the current protocol automatically. Updating source code does not change an existing npm artifact or deploy a server.
20
20
 
21
21
  ## One package, seven entry points
22
22
 
@@ -32,6 +32,39 @@ Version `0.5.0` sends protocol and SDK version headers and validates the protoco
32
32
 
33
33
  The same package supports JavaScript and TypeScript and includes type declarations. Internal `@indiana/contracts` code is bundled; consumers do not install private workspace packages. Swift and Kotlin use their own package ecosystems and are released separately.
34
34
 
35
+ ## API origin and test services
36
+
37
+ The canonical repository policy, including production/test domain roles, package tags and test-host configuration, is [SDK API entry and development testing](../../docs/sdk-endpoints-and-testing.md).
38
+
39
+ Starting with 0.6.0, omitting `baseUrl` selects `https://api.tinker.cash`. This applies to Customer/Server, Funnel, Feature, Paywall, page and visual clients. `createConfigurationSync` also defaults its optional `origin` to that address. Swift `IndianaConfiguration(scope: ...)` and Kotlin `IndianaConfiguration(scope = ...)` source uses the same default; native packages are released separately. Explicit `baseURL` / `baseUrl` still overrides it. Hosted renderer URLs stay on the selected API origin.
40
+
41
+ Both `beta` and `latest` versions published with this default use the production API address. Tags select package versions, not deployments. Our test apps must explicitly set `baseUrl: "https://api.dev.tinker.cash"` and configuration-sync `origin` to the same address. `dev.tinker.cash` is the test main site/Admin, not the Go API entry. The API hosts forward through Vercel to their respective Go Indianapolis services; routing and business readiness have separate acceptance.
42
+
43
+ For our own testing, always require a test-service address instead of relying on the production default:
44
+
45
+ The target test API origin is `https://api.dev.tinker.cash`; `dev.tinker.cash` is the test site/Admin entry. Set `INDIANA_TEST_BASE_URL=https://api.dev.tinker.cash` only after that API domain, forwarding and matching test runtime are accepted. Neither the domain setup nor business readiness is established by this source change. Future `beta` and `latest` SDK releases share the production default; the test host supplies the override.
46
+
47
+ ```ts
48
+ const testBaseUrl = process.env.INDIANA_TEST_BASE_URL;
49
+ if (!testBaseUrl) throw new Error("Set INDIANA_TEST_BASE_URL explicitly");
50
+
51
+ const scope = { projectId: testProjectId, environment: "sandbox" as const };
52
+ const server = createIndianaServer({
53
+ baseUrl: testBaseUrl,
54
+ apiKey: testServerKey,
55
+ scope,
56
+ });
57
+ const customer = createIndiana({ baseUrl: testBaseUrl, scope });
58
+ const configuration = createConfigurationSync({
59
+ origin: testBaseUrl,
60
+ scope: { ...scope, environmentId: testEnvironmentId },
61
+ });
62
+ ```
63
+
64
+ Use the same explicit address for every client in a test run, including Funnel start/controllers and page/visual clients. Environment variables are read by the host, not automatically by the SDK. Sandbox/Live selects business scope inside the chosen service; it does not select a deployment or database. Never retry against another service automatically after an error or unknown command result.
65
+
66
+ Offline tests inject `fetch` and assert the default address, overrides and failure behavior without reaching the public API. Integration tests consume a fixed local SDK tarball against an explicitly selected, protocol-compatible test runtime and retain its version/hash and request IDs. Existing dev protocol 0.5 acceptance does not certify this source's protocol 0.6; Go business checks must wait for its own readiness. See the [maintainer testing procedure](../../docs/sdk-selfserve.md#maintainer-api-origin-testing).
67
+
35
68
  ## Create a Customer Session from a trusted backend
36
69
 
37
70
  ```ts
@@ -67,6 +100,8 @@ const { holdings } = await indiana.readHoldings();
67
100
  indiana.reset();
68
101
  ```
69
102
 
103
+ Indiana maintains an active Customer session through ordinary SDK requests, using the same token and session row. The validity window is 24 hours with a 30-day absolute limit; there is no merchant renewal timer or additional refresh token. Revocation and issuer expiry still apply, and an expired authorization needs a new verified login. This requires the matching session-lifecycle migration, installed on Indiana-test and accepted on dev with the published SDK 0.5.0; production is unchanged by this task. See [lifecycle and evidence](../../docs/customer-credential-storage-proposal.md#automatic-session-lifecycle-2026-10-21).
104
+
70
105
  Customer updates are partial patches and do not use a Customer revision. When a Guest signs in to an existing Customer, the response reports `switched: true`. Resources and history keep their original owner; create a new Session/Funnel for the target Customer. Guest financial facts are not merged.
71
106
 
72
107
  ## Optional stable fingerprint
@@ -103,17 +138,59 @@ In a browser, use `createFunnelRunController` and `showFunnelRun`. The controlle
103
138
 
104
139
  Each Funnel chooses local or remote evaluation. `createFunnelDecisionClient` supports `configuration()`, `features()`, submission, and result reads. `createFeatureClient` identifies configuration by `generation`. Downloaded local rules are visible to clients; use remote evaluation and verified business facts for sensitive decisions.
105
140
 
141
+ ### Bucketing identity across independent consumers
142
+
143
+ Funnel, standalone Features and page experiments evaluate independently. Matching App/environment, experiment definition, attributes, hash identity and Sticky state produce matching results; different refresh times or inputs may produce different variations. A Remote Funnel still evaluates on Indiana when its referenced code Experiment allows Local definition delivery. Accepted Funnel decisions retain their original results. There is no cross-feature result synchronization.
144
+
145
+ For Customer-based evaluation, `id` means the Indiana Customer UUID (`customer.id`); `externalUserId` means the merchant account ID (`customer.externalUserId`). Choose the experiment's `hashAttribute` explicitly when creating it and preserve that meaning in every consumer. Do not put the merchant account ID into `attributes.id`. Supply local attributes from your trusted backend's Customer data; `identify(customerToken)` authenticates requests but does not populate local bucketing attributes. These attributes are evaluation data, not authorization.
146
+
147
+ `setAttributes()` replaces the complete object, so retain the bucketing fields on every update. Anonymous page/visual experiments instead default `id` and `deviceId` to the shared first-party device cookie; they do not automatically match an account-based experiment. Sticky capabilities differ by adapter: managed Local Run instructions receive server state, while pure local tools require their existing host persistence adapter for durable Sticky. No new shared state is introduced. See the [architecture boundary](../../docs/growthbook-sdk-delivery.md#跨消费方求值边界与分桶身份).
148
+
149
+ ### Server-side local Funnel evaluation (unreleased source)
150
+
151
+ For a Local Funnel containing only Entry, Condition, Feature, Split and literal/Feature End or Output nodes, use the explicit local evaluator. Reuse the client/evaluator across requests; create a session for one verified identity and attribute/parameter snapshot:
152
+
153
+ ```ts
154
+ const local = createFunnelDecisionClient(options).local();
155
+ const session = local.session({
156
+ attributes: {
157
+ id: customer.id,
158
+ externalUserId: customer.externalUserId,
159
+ country: "US",
160
+ },
161
+ parameters: { placement: "home" },
162
+ });
163
+ const preview = await session.evaluate();
164
+ const used = await session.evaluate({ track: true });
165
+ // used.value is a local calculation, with no Decision/receipt ID.
166
+ ```
167
+
168
+ `local_configuration` downloads the scoped dependency projection through the existing backend credential checks. Concurrent loads share one request, definitions are prepared once per Version/policy revision/GB generation, and reuse stops at the supplied expiry or 60 seconds, whichever is earlier. `local.refresh()` forces a fresh read; failed refreshes never fall back to stale rules. `local.reset()` invalidates cached definitions and existing sessions. Create a new session after an identity or context change. Browser execution is refused.
169
+
170
+ Sessions retain Sticky assignments in memory. Supply `stickyBucketAssignmentDocs` and `saveStickyBucketAssignmentDoc` for host persistence; the host must scope storage by App/environment/identity and own any cross-process merge/CAS. Async saves finish before a result is returned. A failed save retains the original assignment and rejects with `STICKY_SAVE_FAILED`; retry `session.flush()` or the evaluation. In-memory state alone provides no cross-device consistency.
171
+
172
+ Evaluation is untracked by default. `{ track: true }` invokes configured `onExperimentViewed`/`onFeatureUsage` callbacks once per native assignment/value in that session after successful evaluation and Sticky persistence. Connect callbacks to the existing event tracker only when the result is actually used. Callback errors reach `onError`; event delivery/retries remain the tracker's responsibility, and evaluation does not manufacture durable exposure or usage receipts.
173
+
174
+ Mixed graphs, resource/Bundle outputs and expired configurations fail explicitly. There is no automatic remote execution. `decide()` retains its server-side idempotency, committed receipts and financial checks; local output never grants a Coupon, purchase, entitlement or campaign frequency promise. This source feature requires the matching HTTP implementation and has not been published by this change.
175
+
106
176
  ### Standalone Feature delivery
107
177
 
108
178
  Explicitly grant a backend Key `features:read` / `events:write` and issue existing Customer/Guest Sessions to browser clients.
109
179
 
110
180
  ```ts
111
- const flags = await indiana.features({ attributes: { country: "CA" } });
181
+ // Customer data supplied by your trusted backend for the current user.
182
+ const bucketingIdentity = {
183
+ id: customer.id,
184
+ externalUserId: customer.externalUserId,
185
+ };
186
+ const flags = await indiana.features({
187
+ attributes: { ...bucketingIdentity, country: "CA" },
188
+ });
112
189
  const enabled = flags.getFeatureValue("new-checkout", false);
113
190
  const unsubscribe = flags.subscribe(() =>
114
191
  updateView(flags.isOn("new-checkout")),
115
192
  );
116
- flags.setAttributes({ country: "US" });
193
+ flags.setAttributes({ ...bucketingIdentity, country: "US" });
117
194
  indiana.track("Checkout Viewed", { source: "pricing" });
118
195
  await indiana.flush();
119
196
  // On page teardown:
@@ -151,3 +228,95 @@ The server entry provides AI SDK usage middleware; the OpenRouter entry provides
151
228
  Publishing to npm does not deploy Web services, apply database migrations, or release Swift/Kotlin packages. Package installation, types, offline unit tests, and builds are recorded separately from real Auth/RLS, Stripe, native stores, browser checks, and hosted deployment acceptance.
152
229
 
153
230
  See the [SDK release ledger](https://github.com/tinkerlandai/Indiana/blob/main/docs/sdk-releases.md) for versions and immutable publication evidence, and the [developer documentation](https://tinker.cash/docs?lang=en) for integration guides.
231
+
232
+ ## Shared public configuration (Indiana-test candidate)
233
+
234
+ Use one manager per trusted origin and exact App/environment for Features, canonical Funnel files and enabled page experiments. This source addition is opt-in and requires the matching Indiana-test gateway plus an explicitly reviewed publication. It is not part of the published 0.5.0 package yet.
235
+
236
+ ```ts
237
+ import {
238
+ createConfigurationSync,
239
+ createIndiana,
240
+ } from "@tinkerlandai/indiana-sdk";
241
+
242
+ // Target test API; requires separately accepted domain forwarding and runtime readiness.
243
+ const testApiUrl = "https://api.dev.tinker.cash";
244
+ const scope = { projectId, environment: "sandbox" as const, environmentId };
245
+ const configuration = createConfigurationSync({
246
+ origin: testApiUrl,
247
+ scope,
248
+ });
249
+ const indiana = createIndiana({
250
+ baseUrl: testApiUrl,
251
+ scope: { projectId, environment: "sandbox" },
252
+ configuration,
253
+ });
254
+ const features = await indiana.features();
255
+ const document = await configuration.funnel(funnelId);
256
+ // Other consumers receive this same configuration instance.
257
+ // Supply existing credentials separately for business operations.
258
+ features.destroy();
259
+ configuration.destroy(); // Host owns the shared lifetime.
260
+ ```
261
+
262
+ Pass `configuration` to Funnel start/Run helpers or `createPageExperiments` as well. On your backend, `createSynchronizedFunnelLocalEvaluator(configuration, funnelId)` from `/server` uses the same canonical file/GB generation for pure advisory evaluation. Public bytes never authorize business eligibility or commit a Run transition.
263
+
264
+ Initialization has a five-second total wait budget. Active subscriptions share 12–15-second polling, retries and foreground/reconnect refresh. Local control expires at most 60 seconds after origin validation; expiry or observed pause suspends local evaluation/screens. Cold offline initialization cannot restore active permission from storage. Explicit Indiana Run commands and observations obtain current state from Indiana and bypass CDN initialization and freshness.
265
+
266
+ Optional browser persistence defaults to localStorage and contains only public bytes: 128 entries / 2 MiB / seven days, with a 128-entry / 32 MiB memory cache. Use `storage: null` to disable persistence or supply a `ConfigurationStorage` adapter. Quota errors fall back to memory. Each artifact is limited to 1 MiB, manifests to 512 KiB, and all files are scope/schema/hash checked. Keep credentials, attributes, Sticky records and accepted decisions in their separate existing stores.
267
+
268
+ ## Local Funnel execution
269
+
270
+ Local and Remote share the authoritative Indiana Run, command/action receipts, business services and Queue recovery. Only pure Condition, Feature/Experiment and Feature-based selections change calculation host. Local uses the existing public GB evaluator; Remote calculates inside Indiana and never requires public configuration. The independent pure `evaluateFunnelNode` and advisory `.local()` helpers still perform no business effects.
271
+
272
+ ```ts
273
+ import {
274
+ createLocalFunnelClient,
275
+ showFunnelRun,
276
+ } from "@tinkerlandai/indiana-sdk";
277
+
278
+ const flow = await createLocalFunnelClient({
279
+ baseUrl,
280
+ configuration,
281
+ funnelId,
282
+ credential: customerToken,
283
+ });
284
+ const started = await flow.start({
285
+ requestId: savedStartRequestId, // Persist before sending; reuse the exact input after an unknown response.
286
+ locale: "en",
287
+ parameters: { source: "checkout" },
288
+ });
289
+ const host = await showFunnelRun(container, {
290
+ client: started.controller,
291
+ onError: reportError,
292
+ });
293
+ // Retain started.runToken in your host's secure credential storage.
294
+ // Restore the same Run: await flow.restore(originalRunToken).
295
+ host.destroy(); // Detach UI; accepted backend work remains owned by Indiana.
296
+ started.controller.reset();
297
+ ```
298
+
299
+ Merchant backends use the same adapter from `/server`, with their original Server Key as `credential` and the Indiana Customer UUID as `customerId`. Each Run request proves the original live issuing Key in a server-only header; it is never saved in a pending command or exposed to the renderer. The ordinary `createFunnelRunController` accepts `serverKey` only on a merchant backend. Customer clients continue proving their original Customer identity with `identify(customerToken)`; they cannot override it.
300
+
301
+ The SDK submits each calculation against the exact Run/Version, node, visit/revision, receipt and evaluation-policy revision. Its fixed request ID and frozen result survive a lost reply. Concurrent commands use the existing server fences. A Local result only selects a published exit or declared value; it cannot supply an arbitrary business invocation. Customer Coupon claims still require `offers:resolve` and explicit `selfClaimEnabled`; merchant grants retain `offers:grant`. The existing independent Coupon issuer owns eligibility, inventory, possession and expiry. Standalone `claimCoupon` and `grantOffer` remain available without a Funnel.
302
+
303
+ After a result is accepted, actions, duration/event waits, retries and recovery use the same backend driver, `indiana-funnel-advancement` Queue and Cron repair as Remote. A disconnected host does not cancel an accepted Coupon obligation. A Local condition whose result has not been submitted parks at its receipt and releases the worker; it cannot continue until a Local host returns. The bounded SDK loop settles up to 32 calculation boundaries per call; longer chains continue on a subsequent read. This source cut does not certify latency or sustained capacity.
304
+
305
+ ### Shared UI host
306
+
307
+ Screens, live Customer/Coupon data, shown acknowledgements, Paywall and external-task handling use the same Run controller and UI host. Canonical node/command history uses the common recording limits. The optional `report_progress` upload contract and `funnel_client_events` storage are withdrawn from 0.6; recovery uses the existing authoritative Run, command and business receipts. There is no second host business state machine, `invoke` adapter, Local checkpoint owner, `browserFunnelStorage` or Local presentation controller.
308
+
309
+ Old host checkpoints must be reconciled or drained with their original business identities before switching clients. Do not turn an old execution into a fresh start/request ID. The SDK does not import old checkpoints. On existing test targets the retirement candidate moves the original observation relation into a private read-only archive, preserves every row and continues refusing scoped legacy execution IDs; already accepted managed Runs retain exact replay. It accepts no new observations and creates no new business model. The production packet adds no observation or archive table. A mode change does not rewrite a Run's original policy or accepted commands. New results against a stale policy fail closed; close/reconcile that original Run before starting another intent.
310
+
311
+ The native renderer shell uses the same pure JavaScript calculation adapter and native Run-command bridge; credentials remain in the native session. A headless Swift/Kotlin session exposes the pending calculation and does not silently calculate it remotely. Wire schemas and the renderer shell are generated together. Installing the package does not migrate a database or deploy a runtime. Browser, provider, capacity and native-store acceptance remain separate. See the [unification receipt](../../docs/implementation-receipts/20261020-funnel-shared-execution.json).
312
+
313
+ ## Funnel-scoped resource requests
314
+
315
+ Formal Local Runs submit calculations to Indiana; the canonical executor supplies the original Run/node/visit/receipt when it issues a Coupon or performs commerce. An application consuming a pure Funnel result can bind its Customer client explicitly:
316
+
317
+ ```ts
318
+ const resources = indiana.forFunnel({ funnelId, versionId });
319
+ await resources.claimCoupon(couponId, savedRequestId);
320
+ ```
321
+
322
+ The scoped client automatically carries that fixed Funnel context into Coupon claims, Offering/Paywall resolution and attach quotes. Indiana recomputes only the requested Funnel using trusted facts; caller-reported branches are not permission. Independent `indiana.claimCoupon(...)` remains available under Coupon-owned self-claim rules. Reuse the same request ID and input after an unknown response; accepted commands retain their original result after a later Funnel pause.
package/README.zh-CN.md CHANGED
@@ -2,14 +2,20 @@
2
2
 
3
3
  # @tinkerlandai/indiana-sdk
4
4
 
5
- SDK `0.5.0` 使用协议 `0.5`,新增用量窗口 `anchor` 返回字段、独立发票 `sourceKind: manual` 分类、Invoice 管理方法及订阅 invoice-mode/anchor 输入。anchor 的名称、取值与账期语义来自固定版本 Autumn;发票来源分类是 Indiana 的适配。
5
+ ## Funnel Local / Remote 统一执行
6
6
 
7
- Indiana 的 JavaScript/TypeScript SDK 支持浏览器、Node.js、屏幕渲染及可选 AI 用量集成。此版本需要实现协议 `0.5` 的匹配服务端和数据库;发布 SDK 不会部署服务。服务仍声明协议 `0.3` 时使用 SDK `0.3.1`。
7
+ Local / Remote 只区分纯条件、Feature / 实验和 Feature 选择在哪里计算。正式流程都使用 Indiana 的同一个 Run、command/action 回执、业务服务、幂等和 Queue 恢复。Local SDK 计算结果并提交固定节点命令,后端领取并执行发券、等待、重试;Remote 在 Indiana 计算。Remote 不依赖公共 CDN 初始化。
8
8
 
9
- 协议 0.4 引入的环境采集控制继续保留。当前环境的接入设置分别控制行为事件、实验曝光和 Feature 求值记录。`flush()` 的 `skipped` 表示明确关闭的采集;SDK 缓存配置 30 秒,数据库对每批再次检查。开关不删除历史,也不影响业务计费用量。旧 0.x 协议会在业务执行前被拒绝。
9
+ `createLocalFunnelClient().start({ requestId, locale, parameters })` 返回 `runToken` 和同一个 `controller`,传给 `showFunnelRun` 即可。原 Server Key 的商户服务端另外指定原 Indiana Customer UUID;浏览器使用 Customer Token,不能改写 Customer。恢复必须使用安全保存的原 Run token,不能把旧 Local checkpoint 或未知发券结果转换为一个新请求身份。
10
+
11
+ 条件结果尚未提交时,Run 停车并释放 Worker,仍需 Local 宿主返回;条件结果已受理后,后台业务操作可以通过原来的 Queue / Cron 脱离宿主恢复。Local SDK 不再直接调用发券服务来推进图,也没有第二套宿主业务执行器、定时器和展示状态。独立 Coupon API 保留自己的自领政策、权限、资格、库存、幂等和原有效期,不要求创建 Funnel。
12
+
13
+ 步骤记录共用后端限制;历史 `report_progress` 仍是调用方观察,不能当成发券证明。原生渲染壳复用同一个 JS 纯求值器及原生 Run bridge;无界面 Swift/Kotlin 会明确返回待本地求值,不自动切换 Remote。详细使用和迁移边界见[英文说明](README.md#local-funnel-execution)及[统一回执](../../docs/implementation-receipts/20261020-funnel-shared-execution.json)。安装本包不会自动迁移数据库或部署服务;浏览器、支付渠道、容量和原生商店验收分别记录。
14
+
15
+ 本包版本为 **0.6.0/协议 0.6**,须配套同版本运行时和数据库。Remote Funnel 返回当前页面 JSON,与 Local 共用渲染器,完整 Remote 图和私有规则仍在服务端。最近的 API 入口检查中,生产和测试 Go 服务的业务就绪检查均返回 HTTP 503;npm 发包不会使服务就绪。实际发布、标签和环境状态见[SDK 发布总账](../../docs/sdk-releases.md)。
10
16
 
11
17
  ```sh
12
- npm install --save-exact @tinkerlandai/indiana-sdk@0.5.0
18
+ npm install --save-exact @tinkerlandai/indiana-sdk@0.6.0
13
19
  ```
14
20
 
15
21
  ## 版本兼容与支持
@@ -32,6 +38,20 @@ npm install --save-exact @tinkerlandai/indiana-sdk@0.5.0
32
38
 
33
39
  JavaScript 与 TypeScript 共用本包,声明文件随包提供。内部 `@indiana/contracts` 在构建时内联,接入方无需安装私有包。Swift 和 Kotlin 使用各自的包管理生态,不在本次 npm 发布范围内。
34
40
 
41
+ ## API 默认地址与自测
42
+
43
+ 默认地址、四个域名、版本标签和测试配置的持续规范见 [SDK API 入口与开发测试规范](../../docs/sdk-endpoints-and-testing.md)。
44
+
45
+ 从 0.6.0 起,省略 `baseUrl` 时默认请求 `https://api.tinker.cash`;Customer/Server、Funnel、Feature、Paywall、页面与视觉实验客户端共用此入口。公共配置同步的 `origin` 也可省略并使用同一默认值。Swift `IndianaConfiguration(scope: ...)`、Kotlin `IndianaConfiguration(scope = ...)` 源码同步默认地址,原生包另行发布;仍可显式覆盖 `baseURL`/`baseUrl`。托管 renderer 跟随所选 API origin。
46
+
47
+ 包含此默认值的版本无论标记为 `beta` 或 `latest`,都使用生产 API 地址。标签选择包版本,不选择服务环境。已发布 npm 0.5.0 的示例仍需显式传入与其协议匹配的服务地址,不能将此默认值套用到旧包。两套 API 入口经 Vercel 转发到各自 Go Indianapolis,入口和业务就绪分别验收。
48
+
49
+ 我们自己测试时,宿主必须显式要求 `INDIANA_TEST_BASE_URL`,缺失就报错,不回落生产默认值。同一轮 Server、Customer、Funnel、页面/视觉客户端的 `baseUrl` 和配置同步的 `origin` 使用同一个测试地址;环境变量由宿主读取,SDK 不自动读取。`environment: "sandbox"` 只选择该服务内的业务环境,不选择部署或数据库。
50
+
51
+ 测试 API 地址为 `https://api.dev.tinker.cash`,测试主站/Admin 使用 `dev.tinker.cash`。测试应用配置 `INDIANA_TEST_BASE_URL=https://api.dev.tinker.cash`,业务验收仍须等待测试服务就绪。默认地址保持生产 `https://api.tinker.cash`,由测试应用显式覆盖。
52
+
53
+ 离线测试注入 `fetch`,验证默认地址、覆盖、renderer 地址和网络失败不换服务;集成测试用固定本地 tarball 连接明确选定、协议匹配的测试 runtime,记录包哈希、服务版本和 requestId。既有 dev 协议 0.5 验收不能覆盖当前源码的协议 0.6;Go 业务未就绪时不能用它验收业务,也不自动切回 JS。具体命令见[维护者测试流程](../../docs/sdk-selfserve.md#maintainer-api-origin-testing)。
54
+
35
55
  ## 从可信后端创建 Customer Session
36
56
 
37
57
  ```ts
@@ -67,6 +87,8 @@ const { holdings } = await indiana.readHoldings();
67
87
  indiana.reset();
68
88
  ```
69
89
 
90
+ Indiana 在普通 SDK 请求中自动延续同一 Customer 会话和 Token,默认有效窗口 24 小时、最长 30 天;无需回商户后端的续期定时器,也不增加 Refresh Token。撤销、Key 到期继续生效,授权过期后才需要商户重新认证。此行为需要配套会话迁移;Indiana-test 已安装,并在 dev 使用已发布 SDK 0.5.0 实测,生产本次未改动。见[生命周期与证据](../../docs/customer-credential-storage-proposal.md#automatic-session-lifecycle-2026-10-21)。
91
+
70
92
  Customer 使用原始业务字段;资料更新是部分 patch,没有 Customer revision。Guest 登录到已有 Customer 时返回 `switched: true`,资源和历史保留原归属,接入方应为目标 Customer 创建新 Session/Funnel;不合并旧 Guest 的财务事实。
71
93
 
72
94
  ## Optional stable fingerprint
@@ -104,17 +126,59 @@ On Web, an independent first-party installation cookie can retain the signal. Cl
104
126
 
105
127
  Local/Remote 求值策略由每个 Funnel 决定。`createFunnelDecisionClient` 支持当前 `configuration()`、`features()`、提交和结果读取;`createFeatureClient` 以 `generation` 标识配置。Local 下载的规则对客户端可见;敏感决策应使用 Remote 和可信业务事实。
106
128
 
129
+ ### 独立消费方的分桶身份
130
+
131
+ Funnel、独立 Feature 和页面实验各自求值、各自使用结果。App/环境、实验定义、属性、分桶身份及 Sticky 状态相同时结果应一致;刷新时间或输入不同时允许不同组。Remote Funnel 引用允许 Local 定义投递的代码 Experiment 时,仍由 Indiana 计算。已接受的 Funnel 决定保留原结果,不做跨功能结果同步。
132
+
133
+ Customer 求值中的 `id` 是 Indiana Customer UUID(`customer.id`),`externalUserId` 是商户账号 ID(`customer.externalUserId`)。创建实验时明确选择 `hashAttribute`,每个消费方保持相同语义;不要把商户账号 ID 填进 `attributes.id`。本地属性来自可信后端给当前用户的 Customer 数据;`identify(customerToken)` 负责请求认证,不会自动补齐本地分桶属性。属性仅用于求值,不构成授权。
134
+
135
+ `setAttributes()` 替换整个对象,每次更新都保留分桶字段。匿名页面/视觉实验默认的 `id` 和 `deviceId` 来自共用的第一方设备 cookie,不自动与账号实验同组。各适配器的 Sticky 能力有区别:共用 Run 的 Local 指令读取服务端状态,纯本地工具的持久 Sticky 使用已有宿主适配器,不新增共享状态。完整要求见[架构边界](../../docs/growthbook-sdk-delivery.md#跨消费方求值边界与分桶身份)。
136
+
137
+ ### 服务端 SDK 本地 Funnel 求值(未发布源码)
138
+
139
+ 纯 Local Funnel 支持 Entry、Condition、Feature、Split,以及普通值或 Feature 值的 End/Output。复用 client/evaluator,每个可信身份及属性、参数快照创建独立计算会话:
140
+
141
+ ```ts
142
+ const local = createFunnelDecisionClient(options).local();
143
+ const session = local.session({
144
+ attributes: {
145
+ id: customer.id,
146
+ externalUserId: customer.externalUserId,
147
+ country: "US",
148
+ },
149
+ parameters: { placement: "home" },
150
+ });
151
+ const preview = await session.evaluate();
152
+ const used = await session.evaluate({ track: true });
153
+ // used.value 是本地计算结果,没有 Decision/receipt ID。
154
+ ```
155
+
156
+ `local_configuration` 通过现有后端凭据检查下发依赖投影。并发加载共用一次请求,同一 Version/策略 revision/GB generation 复用准备好的规则;缓存到配置过期或 60 秒即停止,以较早者为准。`local.refresh()` 强制更新,失败后不使用旧规则;`local.reset()` 清空配置并使已有会话失效。身份或上下文改变时创建新会话,浏览器执行会被拒绝。
157
+
158
+ 会话在内存中保留 Sticky。持久化通过 `stickyBucketAssignmentDocs` 和 `saveStickyBucketAssignmentDoc` 接入调用方存储,按 App/环境/身份隔离,并由存储适配器保证跨进程 merge/CAS。异步保存完成后才返回结果;失败保留原分配并抛出 `STICKY_SAVE_FAILED`,可重试 `session.flush()` 或求值。仅有内存状态不保证跨设备一致。
159
+
160
+ 默认求值不触发曝光。仅在确实使用结果时传 `{ track: true }`,成功计算并保存 Sticky 后才调用已配置的 `onExperimentViewed`/`onFeatureUsage`,按本会话内的原生分配/值去重。回调接入已有事件 tracker,异常通过 `onError` 报告;事件送达与重试由 tracker 负责,不生成持久的曝光或使用回执。
161
+
162
+ 含商业节点的混合图、资源/Bundle 输出、过期配置明确拒绝,不自动执行远程 `decide()`。远程幂等、已提交回执和财务检查保持原义;本地结果不能代替发券、付款、权益或全局频控。这是需要配套 HTTP 实现的源码能力,本次没有发布 SDK。
163
+
107
164
  ### 独立 Feature
108
165
 
109
166
  后端 Key 显式授予 `features:read` / `events:write`,通过现有 Customer/Guest Session 交给浏览器,不使用新的凭证种类。
110
167
 
111
168
  ```ts
112
- const flags = await indiana.features({ attributes: { country: "CA" } });
169
+ // 可信后端给当前用户的 Customer 数据。
170
+ const bucketingIdentity = {
171
+ id: customer.id,
172
+ externalUserId: customer.externalUserId,
173
+ };
174
+ const flags = await indiana.features({
175
+ attributes: { ...bucketingIdentity, country: "CA" },
176
+ });
113
177
  const enabled = flags.getFeatureValue("new-checkout", false);
114
178
  const unsubscribe = flags.subscribe(() =>
115
179
  updateView(flags.isOn("new-checkout")),
116
180
  );
117
- flags.setAttributes({ country: "US" });
181
+ flags.setAttributes({ ...bucketingIdentity, country: "US" });
118
182
  indiana.track("Checkout Viewed", { source: "pricing" });
119
183
  await indiana.flush();
120
184
  // 页面卸载时:
@@ -150,3 +214,27 @@ Live 商业执行仍有显式服务端门禁。其他 Product/Price、Entity
150
214
  npm 发布不部署 Web 服务、不应用数据库迁移,也不发布 Swift/Kotlin 包。安装消费、类型、离线单元和构建检查,与真实 Auth/RLS、Stripe、原生商店、浏览器和线上部署分别记录;局部通过不代表整套产品验收。
151
215
 
152
216
  版本、冻结产物哈希和 registry 回读见 [发布总账](../../docs/sdk-releases.md);当前架构与实现边界见 [开发状态](../../docs/development-status.md)。
217
+
218
+ ## 共享公共配置(Indiana-test 候选)
219
+
220
+ 源码新增 `createConfigurationSync({ origin, scope })`;`scope` 必须明确包含 `projectId`、`environment` 和 `environmentId`。将同一个实例作为 `configuration` 传给 `createIndiana`、Funnel start/Run controller 和 `createPageExperiments`,共用清单、文件缓存、并发请求和更新通知。`/server` 导出的 `createSynchronizedFunnelLocalEvaluator(configuration, funnelId)` 使用同一份规范 Funnel 文件与 GB 代次进行纯判断,保留仅可信服务端执行的边界。
221
+
222
+ 此能力需要匹配的 Indiana-test 网关及已审核发布的配置,尚未发布到 npm 0.5.0。公共文件不需要 Customer 凭据;业务操作沿用现有凭据和服务端资格检查。完整调用示例见[英文说明](README.md#shared-public-configuration-indiana-test-candidate)。
223
+
224
+ 初始化总等待不超过 5 秒;活跃消费者共用 12–15 秒轮询,并在回前台/联网后刷新。Local 控制状态自源站验证起最多有效 60 秒,旧 304 或失败不能续期。暂停/过期后停止新本地判断及客户屏幕;冷启动离线时,仅凭持久缓存不能恢复 active 权限。Indiana Run 的命令与观察直接读取服务端状态,独立于 CDN 控制有效期。
225
+
226
+ 内存缓存上限 128 项/32 MiB,浏览器默认 localStorage 的公共文件缓存上限 128 项/2 MiB/7 天。可设置 `storage: null` 或提供 `ConfigurationStorage`;额度不足时退回内存。单文件上限 1 MiB,清单上限 512 KiB,使用前校验作用域、schema 和哈希。共享缓存不存凭据、用户属性、Sticky 或已接受的决策。共享实例由宿主统一 `destroy()`,单个消费者退出不会销毁其他消费者的同步能力。
227
+
228
+ ## 共享 Funnel UI 宿主(未发布源码)
229
+
230
+ `showFunnelRun(container, { client: started.controller })` 在 Local / Remote 都接收同一个服务器 Run controller。屏幕、Customer / Product / Coupon 数据、shown 回执、Paywall、外部任务和未知结果重试共用后端路径。宿主销毁只卸载 UI;停止一个 Run 需要显式提交 Close。Console 私有预览仍是无真实业务效果的模拟。
231
+
232
+ 步骤回执和执行上限共用同一份 Contract 与服务器记录;0.6 撤出 `report_progress` 上传接口和 `funnel_client_events`;恢复使用原有 Run、命令及业务回执。测试候选将现有旧表移入私有只读归档,保留全部数据及旧执行 ID 防重;已接受 Run 仍按原回执恢复,不再接收新观察。生产包不新增观察或归档表。Local Funnel 不再用本地 checkpoint、`flow.signal`、`local.resolveScreen`、`local.openPaywall` 或直调 Coupon API 来持有流程业务义务。独立 `claimCoupon` / `grantOffer` 仍可单独使用,继续由 Coupon 校验资格、额度、权限和有效期。
233
+
234
+ 完整调用和旧执行身份迁移要求见[统一执行说明](README.md#local-funnel-execution)。本轮未部署、应用远端迁移或发布 SDK。
235
+
236
+ ## Funnel 资源上下文
237
+
238
+ 正式 Local Run 由服务端执行原业务动作,自动携带原 Run、node、visit 和 receipt。消费纯 Funnel 结果时,使用 `indiana.forFunnel({ funnelId, versionId })` 获取固定上下文的客户端;其领取、Offering/Paywall 解析和购买报价自动携带这一个 Funnel。服务端以可信事实复算指定流程,不扫描其他 Funnel。
239
+
240
+ 不带上下文的 `indiana.claimCoupon(...)` 仍按 Coupon 自身规则独立领取。未知结果使用原 requestId 和原输入重试;已接受命令在 Funnel 后续暂停时仍返回原结果。
@@ -3286,6 +3286,7 @@ declare const apiOperations: {
3286
3286
  readonly audience: "mixed";
3287
3287
  readonly request: z$1.ZodObject<{
3288
3288
  requestId: z$1.ZodUUID;
3289
+ predecessorOperationId: z$1.ZodOptional<z$1.ZodUUID>;
3289
3290
  invoiceMode: z$1.ZodOptional<z$1.ZodType<InvoiceModeParams, InvoiceModeParamsInput, z$1.core.$ZodTypeInternals<InvoiceModeParams, InvoiceModeParamsInput>>>;
3290
3291
  entityId: z$1.ZodOptional<z$1.ZodUUID>;
3291
3292
  billingSubscriptionId: z$1.ZodOptional<z$1.ZodUUID>;
@@ -3347,6 +3348,8 @@ declare const apiOperations: {
3347
3348
  RON: "RON";
3348
3349
  }>;
3349
3350
  customerId: z$1.ZodOptional<z$1.ZodUUID>;
3351
+ funnelId: z$1.ZodOptional<z$1.ZodUUID>;
3352
+ funnelVersionId: z$1.ZodOptional<z$1.ZodUUID>;
3350
3353
  }, z$1.core.$strict>;
3351
3354
  readonly response: z$1.ZodObject<{
3352
3355
  requestId: z$1.ZodUUID;
@@ -7488,6 +7491,8 @@ declare const apiOperations: {
7488
7491
  }, Record<string, string | number | boolean>>>;
7489
7492
  entityId: z$1.ZodOptional<z$1.ZodUUID>;
7490
7493
  }, z$1.core.$strict>>;
7494
+ funnelId: z$1.ZodOptional<z$1.ZodUUID>;
7495
+ funnelVersionId: z$1.ZodOptional<z$1.ZodUUID>;
7491
7496
  }, z$1.core.$strict>;
7492
7497
  readonly response: z$1.ZodObject<{
7493
7498
  requestId: z$1.ZodUUID;
@@ -8110,6 +8115,8 @@ declare const apiOperations: {
8110
8115
  }, Record<string, string | number | boolean>>>;
8111
8116
  entityId: z$1.ZodOptional<z$1.ZodUUID>;
8112
8117
  }, z$1.core.$strict>>;
8118
+ funnelId: z$1.ZodOptional<z$1.ZodUUID>;
8119
+ funnelVersionId: z$1.ZodOptional<z$1.ZodUUID>;
8113
8120
  }, z$1.core.$strict>;
8114
8121
  readonly response: z$1.ZodObject<{
8115
8122
  requestId: z$1.ZodUUID;
@@ -9672,6 +9679,7 @@ declare const apiOperations: {
9672
9679
  referrerId: z$1.ZodNullable<z$1.ZodUUID>;
9673
9680
  status: z$1.ZodEnum<{
9674
9681
  pending: "pending";
9682
+ unavailable: "unavailable";
9675
9683
  applied: "applied";
9676
9684
  }>;
9677
9685
  result: z$1.ZodNullable<z$1.ZodRecord<z$1.ZodString, z$1.ZodUnknown>>;
@@ -10467,6 +10475,8 @@ declare const apiOperations: {
10467
10475
  }, z$1.core.$strict>;
10468
10476
  }, z$1.core.$strict>>;
10469
10477
  reason: z$1.ZodNullable<z$1.ZodEnum<{
10478
+ unavailable: "unavailable";
10479
+ claim_disabled: "claim_disabled";
10470
10480
  inactive: "inactive";
10471
10481
  outside_window: "outside_window";
10472
10482
  not_targeted: "not_targeted";
@@ -10570,12 +10580,16 @@ declare const apiOperations: {
10570
10580
  readonly method: "POST";
10571
10581
  readonly path: "offers/grant";
10572
10582
  readonly permission: "offers:grant";
10573
- readonly audience: "server";
10574
- /** 服务端定向发放(例如流失召回)。同一 requestId 重试返回同一结果。 */
10583
+ readonly audience: "mixed";
10584
+ readonly customerPermission: "offers:resolve";
10585
+ /** Customer self-claim or merchant-directed grant; retry with the original requestId. */
10575
10586
  readonly request: z$1.ZodObject<{
10576
- customerId: z$1.ZodString;
10577
- couponId: z$1.ZodUUID;
10578
10587
  requestId: z$1.ZodUUID;
10588
+ couponContentDigest: z$1.ZodOptional<z$1.ZodString>;
10589
+ funnelId: z$1.ZodOptional<z$1.ZodUUID>;
10590
+ funnelVersionId: z$1.ZodOptional<z$1.ZodUUID>;
10591
+ customerId: z$1.ZodOptional<z$1.ZodString>;
10592
+ couponId: z$1.ZodUUID;
10579
10593
  }, z$1.core.$strict>;
10580
10594
  readonly response: z$1.ZodObject<{
10581
10595
  requestId: z$1.ZodUUID;
@@ -10659,6 +10673,8 @@ declare const apiOperations: {
10659
10673
  }, z$1.core.$strict>;
10660
10674
  }, z$1.core.$strict>>;
10661
10675
  reason: z$1.ZodNullable<z$1.ZodEnum<{
10676
+ unavailable: "unavailable";
10677
+ claim_disabled: "claim_disabled";
10662
10678
  inactive: "inactive";
10663
10679
  outside_window: "outside_window";
10664
10680
  not_targeted: "not_targeted";
@@ -10835,6 +10851,7 @@ interface FoundationApi {
10835
10851
  customerId: string;
10836
10852
  couponId: string;
10837
10853
  requestId?: string;
10854
+ couponContentDigest?: string;
10838
10855
  }): Promise<ApiOutput<"grantOffer">>;
10839
10856
  /** Trusted backend only. Reuse eventId and all facts for callback retries. */
10840
10857
  recordCouponRedemption(input: ApiInput<"recordCouponRedemption">): Promise<ApiOutput<"recordCouponRedemption">>;
@@ -0,0 +1,4 @@
1
+ import{a as V}from"./chunk-S5TVMQRC.js";import{c as L,f as A,i as T}from"./chunk-S2MSIA57.js";function C(e){if(!/^[A-Za-z0-9_-]{1,64}$/.test(e))throw new Error("Invalid device cookie name");let t="";for(let i of document.cookie.split(";"))if(i.trim().startsWith(`${e}=`))try{t=decodeURIComponent(i.trim().slice(e.length+1))}catch{}return/^[A-Za-z0-9_-]{8,128}$/.test(t)||(t=crypto.randomUUID(),document.cookie=`${e}=${encodeURIComponent(t)}; path=/; max-age=${400*24*3600}; SameSite=Lax`),t}function D(){try{let e=sessionStorage.getItem("indiana_sid");return e||(e=crypto.randomUUID(),sessionStorage.setItem("indiana_sid",e)),e}catch{return}}function $(e=V){let t=new URL(e);if(t.protocol!=="https:"&&!(t.protocol==="http:"&&["localhost","127.0.0.1","[::1]"].includes(t.hostname))||t.username||t.password||t.search||t.hash)throw new Error("baseUrl must be an https origin without credentials");return t.origin}var x=new Set,E;function _(e){if(x.add(e),!E){let t=history.pushState,i=history.replaceState,r=()=>{for(let s of x)s()},o=function(...s){t.apply(this,s),r()},c=function(...s){i.apply(this,s),r()};history.pushState=o,history.replaceState=c,addEventListener("popstate",r),addEventListener("hashchange",r),E=()=>{history.pushState===o&&(history.pushState=t),history.replaceState===c&&(history.replaceState=i),removeEventListener("popstate",r),removeEventListener("hashchange",r)}}return()=>{x.delete(e),x.size||(E?.(),E=void 0)}}var O="__indiana_visual_editor",l;function W(e,t){if(l&&l.origin===e&&l.environmentId===t&&l.expiresAt>Date.now())return l;try{let i=new URL(location.href).searchParams.get(O);if(!i||i.length>2048)return null;let r=JSON.parse(i);return r.origin!==e||r.environmentId!==t||!/^[a-f0-9-]{36}$/i.test(r.nonce)||r.expiresAt<=Date.now()||r.expiresAt>Date.now()+900*1e3?null:(l=r,r)}catch{return null}}function G(e,t){return!!W(e,t)}function Y(){let e=new URL(location.href);e.searchParams.delete(O),history.replaceState(history.state,"",e.href)}/*!
2
+ Portions ported from GrowthBook sdk-js @ ed29827 (GrowthBook.ts auto experiments, util.ts mergeQueryStrings,
3
+ plugins/auto-attributes.ts). Copyright (c) GrowthBook, Inc. MIT License.
4
+ */function B(e,t){let i,r;try{i=new URL(e),r=new URL(t)}catch{return t}return i.searchParams.forEach((o,c)=>{r.searchParams.has(c)||r.searchParams.set(c,o)}),r.toString()}function K(e,t){let i=navigator.userAgent,r=new URLSearchParams(t.search),o={};for(let c of["source","medium","campaign","term","content"]){let s=r.get(`utm_${c}`);s!==null&&(o[`utm${c[0].toUpperCase()}${c.slice(1)}`]=s)}return{id:e,deviceId:e,url:t.href,path:t.pathname,host:t.host,query:t.search,pageTitle:typeof document>"u"?"":document.title,browser:/Edg/.test(i)?"edge":/Chrome/.test(i)?"chrome":/Firefox/.test(i)?"firefox":/Safari/.test(i)?"safari":"unknown",deviceType:/Mobi/.test(i)?"mobile":"desktop",...o}}function Q(e){let t=e;if(!(!!t&&typeof t=="object"&&typeof t.environmentId=="string"&&typeof t.trackExposures=="boolean"&&/^[a-f0-9]{64}$/.test(t.generation??"")&&Array.isArray(t.experiments)&&t.experiments.length<=100&&t.experiments.every(r=>!!r&&!["namespace","status","url","groups"].some(o=>Object.hasOwn(r,o))&&(r.hashVersion===void 0||r.hashVersion===2)&&typeof r.key=="string"&&Array.isArray(r.variations)&&r.variations.length>=2&&r.variations.every(o=>!!o&&typeof o.urlRedirect=="string")&&(r.urlPatterns===void 0||Array.isArray(r.urlPatterns)))&&!!t.savedGroups&&typeof t.savedGroups=="object"&&!Array.isArray(t.savedGroups)))throw new Error("Invalid page experiment payload");return t}function ie(e){if(e.configuration&&e.configuration.scope.environmentId!==e.environmentId)throw new Error("Page configuration belongs to another App environment");let t=$(e.baseUrl);if(!/^[0-9a-f-]{36}$/i.test(e.environmentId))throw new Error("environmentId must be the App environment ID");let i=e.fetch??globalThis.fetch.bind(globalThis),r=`${t}/api/v1/page-experiments/${e.environmentId}`,o=C(e.deviceIdCookie??"indiana_did"),c=D(),s=null,R={...e.attributes??{}},f="",m=!1,I=new Map,P=[],h;function N(n){return n.length?i(`${r}/events`,{method:"POST",body:JSON.stringify({events:n}),headers:{"Content-Type":"text/plain"},keepalive:!0,credentials:"omit"}).then(()=>{}).catch(a=>e.log?.("Exposure not sent",{error:a})):Promise.resolve()}function v(){return clearTimeout(h),h=void 0,N(P.splice(0,20))}let d={trackedExperiments:new Set,log:e.log,onExperimentViewed:(n,a)=>{e.trackExposures===!1||s?.trackExposures===!1||(P.push({eventUuid:crypto.randomUUID(),timestamp:new Date().toISOString(),deviceId:o,...c?{sessionId:c}:{},url:location.href,pageTitle:document.title.slice(0,512),experimentKey:n.key,variationKey:a.key}),h||(h=setTimeout(()=>{v()},1e3)))}},J=()=>{d.url=location.href,d.attributes={...K(o,location),...R}};function H(n){if(!e.disableCrossOriginUrlRedirects)return!1;try{let a=new URL(location.href);return n.variations.some(u=>{if(!u.urlRedirect)return!1;let y=new URL(u.urlRedirect);return y.protocol!==a.protocol||y.host!==a.host})}catch{return!0}}function j(n,a){let u=e.configuration?.snapshot?.generation;f=n;let y=e.navigate??(g=>location.replace(g)),z=e.navigateDelay??(e.navigate?0:100),F=e.maxNavigateDelay??1e3;Promise.all([Promise.race([a,new Promise(g=>setTimeout(g,F))]),new Promise(g=>setTimeout(g,z))]).then(()=>{!m&&(!e.configuration||e.configuration.fresh&&e.configuration.snapshot?.generation===u)&&y(n)})}function p(){if(!(!s||m||e.configuration&&!e.configuration.fresh||G(t,e.environmentId))){J();for(let n of s.experiments){if(H(n)){e.log?.("Auto experiment blocked",{id:n.key});continue}let a;try{a=T(n,d)}catch(u){e.log?.("Skipping page experiment",{id:n.key,error:String(u)});continue}if(I.set(n.key,a),!!a.inExperiment){if(a.value.urlRedirect){let u=n.persistQueryString?B(location.href,a.value.urlRedirect):a.value.urlRedirect;L(u,n.urlPatterns??[])?e.log?.("Skipping redirect because original URL matches redirect URL",{id:n.key}):u!==f&&j(u,v())}break}}}}let w="",q=()=>{location.href!==w&&(w=location.href,f="",p())},S,k;function M(){S??=_(q),addEventListener("pagehide",U)}let U=()=>{v()};async function b(){let n=e.configuration?null:await i(r,{cache:"no-cache",credentials:"omit"});if(n&&!n.ok)throw new Error(`Page experiments unavailable (${n.status})`);let a=Q(e.configuration?await e.configuration.pageExperiments():await n.json());if(a.environmentId.toLowerCase()!==e.environmentId.toLowerCase())throw new Error("Page experiment payload scope changed");s?.trackExposures===!1&&a.trackExposures&&d.trackedExperiments?.clear(),s=a,d.savedGroups=A(a.savedGroups)}return{async start(){await b(),k??=e.configuration?.subscribe(()=>{!m&&e.configuration?.fresh&&b().then(p).catch(n=>e.log?.("Page configuration unavailable",{error:String(n)}))}),M(),w=location.href,p()},async refresh(){await b(),f="",p()},setAttributes(n){R={...n},p()},getResults(){return new Map(I)},get generation(){return s?.generation??null},destroy(){m=!0,k?.(),S?.(),removeEventListener("pagehide",U),v()}}}export{C as a,D as b,$ as c,_ as d,W as e,Y as f,B as g,K as h,Q as i,ie as j};
@@ -0,0 +1 @@
1
+ import{J as o}from"./chunk-RMJJOSVZ.js";var n=class extends Error{constructor(t,s,y,i,r){super(s);this.code=t;this.status=y;this.requestId=i;this.name="IndianaError",this.reason=r?.reason;let e=o(t,y??0,{...r,retryAfter:r?.retryAfterSeconds===void 0?void 0:String(r.retryAfterSeconds)});this.retryable=e.retryable,this.recovery=e.recovery,this.retryAfterSeconds=e.retryAfterSeconds}code;status;requestId;retryable;recovery;retryAfterSeconds;reason};export{n as a};