@yieldfabric/wallet 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @yieldfabric/wallet
2
2
 
3
+ **Proprietary and confidential — Copyright (c) 2026 YieldFabric Technologies Ltd,
4
+ DIFC, UAE. All rights reserved.** Source review requires a signed Source Review
5
+ Authorization incorporating [LICENSE.md](LICENSE.md). Review access permits
6
+ visual inspection only. Obtain the authorization form from YieldFabric.
7
+ The installation, build, and integration instructions below are for YieldFabric
8
+ developers or users with a separate express license permitting those activities.
9
+ See [NOTICE.md](NOTICE.md) for ownership and third-party notices.
10
+
3
11
  Reusable YieldFabric wallet for service developers — login + onboarding,
4
12
  signing (manual MQ + generic API), profile, and key management. Drop
5
13
  `<WalletProvider>` into your React app and the rest of the surface
@@ -7,24 +15,79 @@ signing (manual MQ + generic API), profile, and key management. Drop
7
15
 
8
16
  ## Install
9
17
 
10
- Published to the **public npm registry** — no scope `.npmrc`, no token:
18
+ For authorized runtime use, install the versioned compiled package from npm:
11
19
 
12
20
  ```bash
13
- npm install @yieldfabric/wallet
21
+ npm install --save-exact @yieldfabric/wallet@0.2.0
14
22
  ```
15
23
 
24
+ The package is publicly downloadable from npm; neither repository access nor an
25
+ npm token is required to install it.
26
+ The package contains compiled JavaScript and TypeScript declarations, without
27
+ TypeScript source or source maps. The existing license and notices still apply.
28
+
16
29
  Peer deps: `react ^18`, `react-dom ^18` (and optionally `@stripe/stripe-js` for
17
30
  the Stripe-backed KYC provider). All other runtime deps (`@avatune/*`,
18
31
  `@averer/averer-websdk`, `@heroicons/react`, `js-sha3`, `zod`) install
19
32
  automatically from the public npm registry. The SDK bundles a Tailwind-styled
20
33
  UI; consumers must add it to their Tailwind content glob (see below).
21
34
 
35
+ ## Consumer bundler setup
36
+
37
+ The wallet exposes the built-in provider registry from its root entry. Bundlers
38
+ resolve those provider imports even when the application does not enable every
39
+ provider. For the complete entry, install its provider peers alongside React 18:
40
+
41
+ ```bash
42
+ npm install @dynamic-labs/ethereum@^4 @dynamic-labs/sdk-react-core@^4 @stripe/stripe-js@^9.6 @sumsub/websdk-react@^2.6
43
+ ```
44
+
45
+ The SDK pins `framer-motion@12.10.5`. Its upstream dependency ranges also admit
46
+ newer incompatible Motion internals. Reuse Qord's tested pins in the **consumer**
47
+ `package.json` (npm does not apply dependency packages' own overrides):
48
+
49
+ ```json
50
+ {
51
+ "overrides": {
52
+ "motion-dom": "12.10.5",
53
+ "motion-utils": "12.9.4"
54
+ }
55
+ }
56
+ ```
57
+
58
+ The currently used `@averer/averer-websdk@2.1.3` package exports
59
+ `dist/averer-websdk.es`, but ships `dist/averer-websdk.es.js`. Until that upstream
60
+ package fixes its export, add an alias in the consumer's bundler configuration.
61
+ For an ESM Vite configuration:
62
+
63
+ ```js
64
+ import { createRequire } from 'node:module';
65
+ import { dirname, join } from 'node:path';
66
+ const require = createRequire(import.meta.url);
67
+ const avererEntry = join(
68
+ dirname(require.resolve('@averer/averer-websdk/package.json')),
69
+ 'dist/averer-websdk.es.js',
70
+ );
71
+ // Merge into the existing Vite configuration:
72
+ // resolve: { alias: [{ find: /^@averer\/averer-websdk$/, replacement: avererEntry }] }
73
+ ```
74
+
75
+ Webpack/CRA applications can use the same `avererEntry` as the exact-match alias
76
+ `'@averer/averer-websdk$'`. This resolves the installed npm artifact; it needs no
77
+ YieldFabric source checkout. Keep the Tailwind content paths below pointing at
78
+ the installed `dist` files.
79
+
22
80
  ## Quick start
23
81
 
24
82
  ```tsx
25
83
  // app/layout.tsx (Next.js App Router)
26
84
  'use client';
27
- import { WalletProvider, SignatureWorkflow } from '@yieldfabric/wallet';
85
+ import {
86
+ CreditExhaustedHost,
87
+ SignatureWorkflow,
88
+ WalletCenter,
89
+ WalletProvider,
90
+ } from '@yieldfabric/wallet';
28
91
  import { useRouter } from 'next/navigation';
29
92
 
30
93
  export default function RootLayout({ children }: { children: React.ReactNode }) {
@@ -42,14 +105,17 @@ export default function RootLayout({ children }: { children: React.ReactNode })
42
105
  }}
43
106
  >
44
107
  {children}
45
- {/* Zero-config global mount — wires the wallet's signature
46
- queue, drawer, toast notifier, and (optionally) the
47
- Signature Center launcher. This is the component every
48
- host wants at the root; <SignatureDrawer /> by itself is
108
+ {/* Signing and Wallet Center are intentionally separate:
109
+ SignatureWorkflow owns queue/toast/signer state;
110
+ WalletCenter owns the configurable wallet shell.
111
+ <SignatureDrawer /> by itself is
49
112
  a *controlled inner* that needs `message` / `onClose` /
50
113
  `userId` props and is rendered by <SignatureWorkflow>
51
114
  for each pending message. */}
52
115
  <SignatureWorkflow />
116
+ <WalletCenter />
117
+ {/* Required for prepaid plan/top-up return reconciliation. */}
118
+ <CreditExhaustedHost />
53
119
  </WalletProvider>
54
120
  </body>
55
121
  </html>
@@ -57,6 +123,56 @@ export default function RootLayout({ children }: { children: React.ReactNode })
57
123
  }
58
124
  ```
59
125
 
126
+ ### Configuring Wallet Center
127
+
128
+ The SDK owns the shell and responsive behavior; the consuming app supplies an
129
+ ordered module registry. Built-ins, app routes, app panels, and one searchable
130
+ directory compose without special-case props:
131
+
132
+ ```tsx
133
+ import {
134
+ WalletCenter,
135
+ WalletCenterTrigger,
136
+ defineWalletDirectory,
137
+ defineWalletPanel,
138
+ defineWalletRoute,
139
+ walletCenter,
140
+ } from '@yieldfabric/wallet';
141
+
142
+ const modules = [
143
+ walletCenter.transactions(),
144
+ walletCenter.profile(),
145
+ walletCenter.keys(),
146
+ walletCenter.groups(),
147
+ defineWalletPanel({
148
+ kind: 'panel', id: 'accounts', label: 'Accounts', icon: WalletIcon,
149
+ render: ({ close }) => <Accounts onDone={close} />,
150
+ }),
151
+ defineWalletRoute({
152
+ kind: 'route', id: 'billing', label: 'Billing', icon: CardIcon,
153
+ href: '/billing',
154
+ }),
155
+ walletCenter.history(),
156
+ defineWalletDirectory({
157
+ kind: 'directory', id: 'app', label: 'Explore', icon: GridIcon,
158
+ groups: appNavigationGroups,
159
+ }),
160
+ ];
161
+
162
+ <WalletCenterTrigger />
163
+ <WalletCenter config={{ modules, actors, identity }} />
164
+ ```
165
+
166
+ Array order is display order. Route selection closes before using the host's
167
+ `onNavigate`; panel renderers receive `{ close, back, navigate }`; directory
168
+ groups may be mobile- or desktop-only. `useWalletCenter()` is the headless
169
+ open/close API for avatar buttons and other custom triggers. It also lets a
170
+ frame *host* the center: `setHost(element)` makes `<WalletCenter>` render
171
+ inside that element as a page of the host's own frame (no overlay, no close
172
+ control — the frame's own navigation is the way out) instead of as a drawer
173
+ over the app; `setHost(null)` on unmount hands it back. A phone frame with a
174
+ Wallet tab is the intended host.
175
+
60
176
  ```tsx
61
177
  // app/login/page.tsx
62
178
  'use client';
@@ -78,7 +194,10 @@ build time (see [`src/core/config.ts`](./src/core/config.ts)):
78
194
  |--|--|--|
79
195
  | `REACT_APP_AUTH_SERVICE_URL` | `http://localhost:3000` | auth REST |
80
196
  | `REACT_APP_PAYMENTS_SERVICE_URL` | `http://localhost:3002` | payments + WebSocket |
81
- | `REACT_APP_API_URL` | `http://localhost:3002` | shared API base (WS derivation) |
197
+ | `REACT_APP_PAYMENTS_URLS` | — | JSON map of chain id to payments base URL |
198
+ | `REACT_APP_TEST_CHAIN_IDS` | — | comma-separated chains displayed as Test mode |
199
+ | `REACT_APP_ENABLED_PAYMENT_CHAIN_IDS` | — | comma-separated chains currently open to user switching |
200
+ | `REACT_APP_API_URL` | `http://localhost:3002` | legacy single-instance WebSocket base |
82
201
  | `REACT_APP_CORE_SERVICE_URL` | `http://localhost:3002` | core service URL |
83
202
  | `REACT_APP_CHAIN_ID` | `31337` | default chain id |
84
203
 
@@ -88,14 +207,82 @@ How to expose them to the browser depends on your bundler:
88
207
  - **Vite**: define them with `define: { 'process.env.REACT_APP_*': ... }` in `vite.config.ts`, or fork `core/config.ts` to read `import.meta.env.VITE_*`.
89
208
  - **Next.js**: prefix with `NEXT_PUBLIC_` in `.env.local` AND map them in `next.config.js`'s `env` block so `process.env.REACT_APP_*` resolves in client bundles.
90
209
 
91
- Future work: a follow-up will move URL resolution onto the
92
- `WalletRuntimeConfig` so consumers can pass URLs at mount time and
93
- not rely on env-var plumbing. See the comment at the top of
94
- `core/config.ts`.
210
+ Dual-instance deployments can configure the same values at runtime instead
211
+ of baking CRA variables into the bundle. The active JWT's
212
+ `default_chain_id` selects the URL; `enabledChainIds` can keep a published
213
+ mainnet topology disabled until its contracts are deployed:
214
+
215
+ ```tsx
216
+ <WalletProvider
217
+ config={{
218
+ paymentsUrls: {
219
+ '151': 'https://pay.live.yieldfabric.com',
220
+ '153': 'https://pay.test.yieldfabric.com',
221
+ },
222
+ testChainIds: ['153'],
223
+ enabledChainIds: ['153'], // add '151' when mainnet is ready
224
+ }}
225
+ >
226
+ {children}
227
+ </WalletProvider>
228
+ ```
229
+
230
+ Payment mutations, settlement polling, SSE and WebSockets must all resolve
231
+ their base URL at request/connection time. Decoding the JWT in the browser is
232
+ only a routing hint; the selected payments instance still validates the
233
+ signature and enforces chain affinity.
95
234
 
96
235
  The full public surface — providers, hooks, UI components, core REST/crypto
97
236
  services, types — is enumerated in [`src/index.ts`](./src/index.ts).
98
237
 
238
+ ### Prepaid plan onboarding and card Checkout
239
+
240
+ `<OnboardingWizard>` uses a two-stage commercial flow for direct YieldFabric
241
+ signups. At `/onboarding`, the unauthenticated public catalog discloses the
242
+ exact monthly renewal and cycle credit before the user chooses an auth method.
243
+ That choice is intent only: no Stripe Customer is created and no card is
244
+ collected. Once authentication succeeds, the SDK best-effort persists the
245
+ choice to `/auth/users/me/onboarding-plan-intent` so refresh/resume works.
246
+
247
+ After email, key, identity, and profile gates, `/onboarding/plan` resolves the
248
+ intent against the current catalog and repeats the terms for final
249
+ confirmation. Personal plans then redirect to Stripe-hosted, card-only
250
+ Checkout. A required prepaid cohort cannot defer this final step. Platform-
251
+ tagged end users skip the self-pay flow. To offer group plans, inject
252
+ `onSelectPlatform(planId)` and have the host create its billing group,
253
+ subscribe that exact group SKU, then configure sponsorship:
254
+
255
+ ```tsx
256
+ <OnboardingWizard
257
+ initialPathname={pathname}
258
+ onSelectPlatform={(planId) => openPlatformSetup({ planId })}
259
+ />
260
+ <CreditExhaustedHost />
261
+ ```
262
+
263
+ Mount exactly one `<CreditExhaustedHost />` inside `<WalletProvider>` on every
264
+ authenticated layout, including the route used as Checkout's success return.
265
+ The host reads the server-appended Checkout Session id, polls the authenticated
266
+ durable status with a bounded retry schedule, refreshes the exact personal or
267
+ group payer, and re-mints tokens only after `applied: true`. It also clears
268
+ terminal or inconsistent markers and tells the user when a purchase failed,
269
+ was reversed/refunded, or still needs later confirmation. Without this host, a
270
+ successful Stripe redirect cannot heal a gated browser session.
271
+
272
+ Onboarding plan Checkout returns to `/onboarding/plan`, not directly to the
273
+ billing page or home. The wizard advances to Welcome only after the exact
274
+ Checkout Session is durably `applied` and the refreshed billing summary holds
275
+ that plan.
276
+
277
+ Use `buildCreditCheckoutReturnUrls()` when calling the lower-level
278
+ `billingService.createCreditCheckout()` or `createTopupCheckout()` APIs. The
279
+ success and cancel URLs must be same-origin; the success marker must encode the
280
+ exact purchase kind and payer. Pass a canonical group UUID both to the URL
281
+ builder and the Checkout method for a group purchase—never infer a group from
282
+ the active delegation. Preserve the success query string through host routing
283
+ so the return host can verify it. A complete unresolved return marker blocks a
284
+ second Checkout until it becomes terminal or is checked again after reload.
285
+
99
286
  ## Login surface API
100
287
 
101
288
  The login screen is built from three independently-mountable pieces:
@@ -141,7 +328,7 @@ Three drop-in components, ordered most opinionated → least:
141
328
 
142
329
  | Component | Use when |
143
330
  |---|---|
144
- | `<IncomingPaymentsLauncher />` | You want a nav-chip with a badge count. Click opens an internal drawer with the full inbox. Mirrors `<SignatureCenterLauncher>` in shape. |
331
+ | `<IncomingPaymentsLauncher />` | You want a nav-chip with a badge count. Click opens an internal drawer with the full inbox. Mirrors `<WalletCenterTrigger>` in shape. |
145
332
  | `<IncomingPaymentsCenter />` | You want a dedicated `/payments/inbox`-style page. Adds a header, refresh button, and summary tiles on top of the inbox. |
146
333
  | `<IncomingPaymentsInbox />` | You want just the card list — embed in your own chrome (drawer, dashboard card, sidebar). |
147
334
  | `useIncomingPayments()` | You want full UI control. Returns `{ items, groups, accept, acceptState, loading, error, refetch, activeEntityId, … }`. |
@@ -186,13 +373,131 @@ scopes every query and mutation to the delegated group. No prop
186
373
  threading required. The resolution order for the active entity is:
187
374
 
188
375
  1. Explicit `entityId` prop on the hook / component (if passed).
189
- 2. `localStorage.delegation_group_id` — set by `<AppAuthProvider>` when
376
+ 2. `localStorage.delegation_group_id` — maintained by `<WalletProvider>` when
190
377
  delegation is active.
191
378
  3. `useAuth().user.id` — the authenticated user's own entity.
192
379
 
193
380
  Hosts that maintain their own actingAs model can pass `entityId`
194
381
  explicitly to override the auto-detection.
195
382
 
383
+ For a confirmed ownership edge `Group A → Group B`, the Wallet Center also
384
+ supports one ownership-derived hop. Start in the direct A session, then call:
385
+
386
+ ```ts
387
+ const {
388
+ delegationSession,
389
+ assumeOwnedGroup,
390
+ returnToParent,
391
+ } = useAuthContext();
392
+
393
+ // Select one active ECDSA USER key owned by the human operator. A group key
394
+ // is never eligible. Custodial keys execute through Automatic; external keys
395
+ // use the Manual signature drawer.
396
+ const userKeys = await keysService.getUserKeyPairs(currentUser.id);
397
+ const signer = userKeys.find(
398
+ (key) => ownedGroupUserSignerCustody(key, currentUser.id) !== null,
399
+ );
400
+ if (!signer) throw new Error('No eligible personal signing key');
401
+
402
+ // Mints a non-refreshable acting-as-B child and preserves direct A locally.
403
+ await assumeOwnedGroup(groupB, signer.id);
404
+
405
+ // Restores A only while its preserved access token is still live.
406
+ returnToParent();
407
+ ```
408
+
409
+ `KeyPair.account_signer_custody` is a non-secret, server-authoritative field:
410
+ `custodial` routes the selected human key through Automatic execution,
411
+ `external` routes it through the Manual wallet drawer, and `null` means the key
412
+ is not eligible for the account ECDSA65 signature profile. The SDK fails closed
413
+ when the field is missing and never infers sealed-key availability from
414
+ `provider_type`, `key_type`, or metadata.
415
+
416
+ `delegationSession` is a discriminated `direct | owned` lineage record. An
417
+ owned child stores the exact immutable `membershipEdge`, the selected
418
+ `signerKeyId`/address/custody, and `You → A → B` path. It rejects the removed
419
+ `authority_key_id` model. Owned children are capped at 15 minutes, cannot be
420
+ nested again, and never send `X-Refresh-Token`. The SDK automatically returns
421
+ to a still-live A when B expires; otherwise it clears delegation state.
422
+
423
+ In the concrete **Head Co → Subsidiary** UX:
424
+
425
+ 1. A Subsidiary Owner selects **Act as Subsidiary**, opens Subsidiary,
426
+ enters the Head Co group UUID under **Owning groups**, and reserves the
427
+ relationship.
428
+ 2. A Head Co Owner selects **Act as Head Co**, enters the
429
+ Subsidiary group UUID under **Relationship setup**, and advances the
430
+ immutable NFT mint with their ordinary USER key.
431
+ 3. After that operation settles, the Subsidiary Owner returns to
432
+ **Act as Subsidiary** and advances the same relationship again. This adds
433
+ the Head Co-owned NFT as a Subsidiary member; repeat after settlement until
434
+ the row is `active`.
435
+ 4. The Head Co operator selects **Act as Head Co**. Wallet Center now lists
436
+ Subsidiary under **Owned group accounts** and asks which personal key will
437
+ authorize the child. A custodial USER key selects Automatic; an external
438
+ USER key selects Manual.
439
+ 5. **Operate as Subsidiary** installs the child and displays
440
+ `You → Head Co → Subsidiary`.
441
+ 6. **Return to Head Co** restores the preserved direct parent; **Act as self**
442
+ clears the complete delegation lineage.
443
+
444
+ “Head Co operator” in the relationship ceremony means a live Head Co Owner,
445
+ not every Head Co member/admin and not the platform `UserRole::Operator` by
446
+ name. The narrowly defined staff fallback requires `ManageGroupOwners`.
447
+ Ordinary consumer Owners do not need that global permission. Head Co's members
448
+ do not become Subsidiary members. The on-chain authority is the NFT edge,
449
+ while each transaction remains attributable to the selected human EOA;
450
+ neither Head Co nor Subsidiary needs a YieldFabric-custodied group signing key.
451
+
452
+ Headless clients can discover B with
453
+ `groupsService.getOwnedGroups(groupAId, directAToken)` and perform the strict
454
+ exchange with `delegationService.assumeOwnedGroup(request, directAToken)`.
455
+ `request.signer_key_id` is mandatory and must name the operator's active USER
456
+ key. Relationship lifecycle calls are deliberately separate from raw owner
457
+ management:
458
+
459
+ ```ts
460
+ await groupsService.establishGroupOwner(childBId, parentAId, chainId, userBToken);
461
+ await groupsService.establishGroupOwner(childBId, parentAId, chainId, userAToken);
462
+ await groupsService.establishGroupOwner(childBId, parentAId, chainId, userBToken);
463
+ const status = await groupsService.getGroupOwners(childBId, chainId, userBToken);
464
+ ```
465
+
466
+ Those calls form an idempotent, multi-authority saga: B reserves, A mints, and
467
+ B adds the credential. POST/DELETE deliberately take the human's personal JWT;
468
+ the explicit parent/child IDs identify the stage, while any resulting MQ
469
+ message is still owned by the relevant group. Never substitute `add-owner`, a
470
+ group key, or a platform-held key for this flow.
471
+
472
+ Consumer apps should reuse the SDK panel instead of rebuilding the stage
473
+ machine or exposing the legacy raw-owner/group-key form:
474
+
475
+ ```tsx
476
+ <GroupOwnershipPanel
477
+ childGroup={subsidiary}
478
+ chainId={activeChainId}
479
+ accountReady={subsidiaryAccount.status === 'ready'}
480
+ currentRole={subsidiary.role}
481
+ activeDirectGroupId={delegationSession?.kind === 'direct'
482
+ ? delegationSession.currentGroupId
483
+ : null}
484
+ availableParentGroups={groups}
485
+ onChanged={() => refreshGroups()}
486
+ />
487
+ ```
488
+
489
+ The panel is fail-closed and stage-aware. Only a live child `owner` sees
490
+ mutations; `admin` is insufficient. It reserves and advances child membership
491
+ with POST, reconciles revocation with DELETE, disables setup until the child's
492
+ chain account is ready, and labels any parent groups the host already knows.
493
+ The parent mint stage remains in the direct parent context's
494
+ **Relationship setup** card because an A-only operator is intentionally unable
495
+ to list B's private relationship state.
496
+
497
+ The canonical business model, setup calls, exact JWT claims, lifetime rules,
498
+ revocation behaviour, and troubleshooting are documented in
499
+ [Group account ownership: Head Co → Subsidiary](../yieldfabric-auth/docs/api/guides/group-account-ownership.md).
500
+
196
501
  #### Signature requirement
197
502
 
198
503
  Accepting a payment or obligation enqueues an on-chain transaction
@@ -200,8 +505,9 @@ through the MQ. When the user is in **Manual** signature mode the
200
505
  SDK's signature drawer prompts them to sign before the executor
201
506
  fires. Consumers using this inbox **must mount
202
507
  `<SignatureWorkflow />`** once at the app root — that's the zero-prop
203
- component that wires the queue + drawer + toast + Signature Center
204
- launcher together. `<SignatureDrawer />` by itself is a controlled
508
+ component that wires the queue + drawer + toast together. Mount
509
+ `<WalletCenter />` separately when the app exposes the wallet shell.
510
+ `<SignatureDrawer />` by itself is a controlled
205
511
  inner that takes `message` / `onClose` / `userId` props; you don't
206
512
  mount it directly.
207
513
 
@@ -607,7 +913,7 @@ Import graph: `ui → react → providers → core`. Forbid backward edges.
607
913
  ## Build
608
914
 
609
915
  ```bash
610
- npm install
916
+ npm ci --ignore-scripts
611
917
  npm run build # one-shot — emits dist/index.{js,cjs,d.ts}
612
918
  npm run dev # watch mode — recompiles on save
613
919
  npm run typecheck # tsc --noEmit
@@ -623,9 +929,9 @@ npm run clean # rm -rf dist
623
929
  Both bundles are prepended with `"use client";` so Next.js App Router
624
930
  treats the SDK as client-side by default (the surface is hook + DOM-heavy).
625
931
 
626
- Externals: `react`, `react-dom`, and the runtime deps (`@averer/averer-websdk`,
627
- `@heroicons/react`, `js-sha3`, `zod`) are NOT bundled — consumers install
628
- them via the `dependencies` / `peerDependencies` declarations.
932
+ React peers and runtime dependencies declared in `package.json` stay external
933
+ to both bundles. Consumers install them through the `dependencies` /
934
+ `peerDependencies` declarations.
629
935
 
630
936
  ## Consuming from another monorepo package
631
937
 
@@ -658,23 +964,43 @@ If you're iterating on the SDK while running the consumer, leave
658
964
  `npm run dev` running in the SDK directory; the consumer's bundler will
659
965
  pick up changes on the next reload.
660
966
 
661
- ## Publishing
967
+ ## Controlled distribution
662
968
 
663
- Ships to the **public npm registry** as a public scoped package
664
- (`publishConfig.access: public`). Publishing `@yieldfabric/*` requires the
665
- `yieldfabric` scope on npm — i.e. an npm org (or user) named `yieldfabric`.
666
- Authenticate once with `npm login`, then:
969
+ Publication targets the npm registry using the existing `prepublishOnly` build
970
+ hook. `publishConfig.access: public` publishes the compiled artifact publicly.
971
+ The source repository retains its separate Git hosting access controls.
972
+ An authorized release requires an unused version and npm publish rights.
973
+
974
+ For YieldFabric's authorized preparation of a runtime artifact:
667
975
 
668
976
  ```bash
669
- # bump "version" first — re-publishing an existing version is rejected
670
- npm publish # prepublishOnly runs clean && build automatically
977
+ npm run build
978
+ npm pack --dry-run --ignore-scripts
671
979
  ```
672
980
 
673
- `prepublishOnly` runs `clean && build`, so a stale `dist/` can't leak into the
674
- tarball. The published tarball ships **`dist/` + `README.md` only** — no `src/`
675
- and no source maps, so the TypeScript source stays closed (only compiled
676
- output ships). Verify with `npm pack --dry-run`.
981
+ The package allowlist contains only `dist/index.js`, `dist/index.cjs`,
982
+ `dist/index.d.ts`, `dist/index.d.cts`, `README.md`, `LICENSE.md`, and `NOTICE.md`
983
+ (npm also includes `package.json`). Source and declaration maps are disabled.
984
+ Built JavaScript and declarations still disclose implementation information;
985
+ they require an appropriate license and delivery decision.
986
+
987
+ Customer source review follows the source repository's `SOURCE_ACCESS.md` guide,
988
+ including a signed authorization and a controlled viewer. The source-review
989
+ agreement does not grant runtime or integration rights.
677
990
 
678
991
  ## License
679
992
 
680
- Proprietary. See repo root for terms.
993
+ [YieldFabric Proprietary Source Code Review and Non-Use Agreement](LICENSE.md).
994
+ Ownership remains with YieldFabric Technologies Ltd, subject to the
995
+ [third-party notices](NOTICE.md). No rights are granted merely by repository
996
+ access, possession of a package, or the technical examples in this README.
997
+
998
+ ### Authenticated JSON event streams
999
+
1000
+ `subscribeJsonEvents({ url, token, onEvent, onError })` returns an unsubscribe
1001
+ function. `url` and `token` are callbacks evaluated on every connection, so a
1002
+ reconnect uses the current token and service context. It sends an Authorization
1003
+ header, never a token query parameter. Errors reconnect with bounded backoff;
1004
+ call unsubscribe on sign-out, scope changes and component unmount. The stream
1005
+ uses fetch and therefore requires the server's CORS configuration to permit
1006
+ Authorization headers. Entity subscriptions use this helper automatically.