@yieldfabric/wallet 0.1.0 → 0.2.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.
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,51 @@ 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.
170
+
60
171
  ```tsx
61
172
  // app/login/page.tsx
62
173
  'use client';
@@ -78,7 +189,10 @@ build time (see [`src/core/config.ts`](./src/core/config.ts)):
78
189
  |--|--|--|
79
190
  | `REACT_APP_AUTH_SERVICE_URL` | `http://localhost:3000` | auth REST |
80
191
  | `REACT_APP_PAYMENTS_SERVICE_URL` | `http://localhost:3002` | payments + WebSocket |
81
- | `REACT_APP_API_URL` | `http://localhost:3002` | shared API base (WS derivation) |
192
+ | `REACT_APP_PAYMENTS_URLS` | — | JSON map of chain id to payments base URL |
193
+ | `REACT_APP_TEST_CHAIN_IDS` | — | comma-separated chains displayed as Test mode |
194
+ | `REACT_APP_ENABLED_PAYMENT_CHAIN_IDS` | — | comma-separated chains currently open to user switching |
195
+ | `REACT_APP_API_URL` | `http://localhost:3002` | legacy single-instance WebSocket base |
82
196
  | `REACT_APP_CORE_SERVICE_URL` | `http://localhost:3002` | core service URL |
83
197
  | `REACT_APP_CHAIN_ID` | `31337` | default chain id |
84
198
 
@@ -88,14 +202,82 @@ How to expose them to the browser depends on your bundler:
88
202
  - **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
203
  - **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
204
 
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`.
205
+ Dual-instance deployments can configure the same values at runtime instead
206
+ of baking CRA variables into the bundle. The active JWT's
207
+ `default_chain_id` selects the URL; `enabledChainIds` can keep a published
208
+ mainnet topology disabled until its contracts are deployed:
209
+
210
+ ```tsx
211
+ <WalletProvider
212
+ config={{
213
+ paymentsUrls: {
214
+ '151': 'https://pay.live.yieldfabric.com',
215
+ '153': 'https://pay.test.yieldfabric.com',
216
+ },
217
+ testChainIds: ['153'],
218
+ enabledChainIds: ['153'], // add '151' when mainnet is ready
219
+ }}
220
+ >
221
+ {children}
222
+ </WalletProvider>
223
+ ```
224
+
225
+ Payment mutations, settlement polling, SSE and WebSockets must all resolve
226
+ their base URL at request/connection time. Decoding the JWT in the browser is
227
+ only a routing hint; the selected payments instance still validates the
228
+ signature and enforces chain affinity.
95
229
 
96
230
  The full public surface — providers, hooks, UI components, core REST/crypto
97
231
  services, types — is enumerated in [`src/index.ts`](./src/index.ts).
98
232
 
233
+ ### Prepaid plan onboarding and card Checkout
234
+
235
+ `<OnboardingWizard>` uses a two-stage commercial flow for direct YieldFabric
236
+ signups. At `/onboarding`, the unauthenticated public catalog discloses the
237
+ exact monthly renewal and cycle credit before the user chooses an auth method.
238
+ That choice is intent only: no Stripe Customer is created and no card is
239
+ collected. Once authentication succeeds, the SDK best-effort persists the
240
+ choice to `/auth/users/me/onboarding-plan-intent` so refresh/resume works.
241
+
242
+ After email, key, identity, and profile gates, `/onboarding/plan` resolves the
243
+ intent against the current catalog and repeats the terms for final
244
+ confirmation. Personal plans then redirect to Stripe-hosted, card-only
245
+ Checkout. A required prepaid cohort cannot defer this final step. Platform-
246
+ tagged end users skip the self-pay flow. To offer group plans, inject
247
+ `onSelectPlatform(planId)` and have the host create its billing group,
248
+ subscribe that exact group SKU, then configure sponsorship:
249
+
250
+ ```tsx
251
+ <OnboardingWizard
252
+ initialPathname={pathname}
253
+ onSelectPlatform={(planId) => openPlatformSetup({ planId })}
254
+ />
255
+ <CreditExhaustedHost />
256
+ ```
257
+
258
+ Mount exactly one `<CreditExhaustedHost />` inside `<WalletProvider>` on every
259
+ authenticated layout, including the route used as Checkout's success return.
260
+ The host reads the server-appended Checkout Session id, polls the authenticated
261
+ durable status with a bounded retry schedule, refreshes the exact personal or
262
+ group payer, and re-mints tokens only after `applied: true`. It also clears
263
+ terminal or inconsistent markers and tells the user when a purchase failed,
264
+ was reversed/refunded, or still needs later confirmation. Without this host, a
265
+ successful Stripe redirect cannot heal a gated browser session.
266
+
267
+ Onboarding plan Checkout returns to `/onboarding/plan`, not directly to the
268
+ billing page or home. The wizard advances to Welcome only after the exact
269
+ Checkout Session is durably `applied` and the refreshed billing summary holds
270
+ that plan.
271
+
272
+ Use `buildCreditCheckoutReturnUrls()` when calling the lower-level
273
+ `billingService.createCreditCheckout()` or `createTopupCheckout()` APIs. The
274
+ success and cancel URLs must be same-origin; the success marker must encode the
275
+ exact purchase kind and payer. Pass a canonical group UUID both to the URL
276
+ builder and the Checkout method for a group purchase—never infer a group from
277
+ the active delegation. Preserve the success query string through host routing
278
+ so the return host can verify it. A complete unresolved return marker blocks a
279
+ second Checkout until it becomes terminal or is checked again after reload.
280
+
99
281
  ## Login surface API
100
282
 
101
283
  The login screen is built from three independently-mountable pieces:
@@ -141,7 +323,7 @@ Three drop-in components, ordered most opinionated → least:
141
323
 
142
324
  | Component | Use when |
143
325
  |---|---|
144
- | `<IncomingPaymentsLauncher />` | You want a nav-chip with a badge count. Click opens an internal drawer with the full inbox. Mirrors `<SignatureCenterLauncher>` in shape. |
326
+ | `<IncomingPaymentsLauncher />` | You want a nav-chip with a badge count. Click opens an internal drawer with the full inbox. Mirrors `<WalletCenterTrigger>` in shape. |
145
327
  | `<IncomingPaymentsCenter />` | You want a dedicated `/payments/inbox`-style page. Adds a header, refresh button, and summary tiles on top of the inbox. |
146
328
  | `<IncomingPaymentsInbox />` | You want just the card list — embed in your own chrome (drawer, dashboard card, sidebar). |
147
329
  | `useIncomingPayments()` | You want full UI control. Returns `{ items, groups, accept, acceptState, loading, error, refetch, activeEntityId, … }`. |
@@ -186,13 +368,131 @@ scopes every query and mutation to the delegated group. No prop
186
368
  threading required. The resolution order for the active entity is:
187
369
 
188
370
  1. Explicit `entityId` prop on the hook / component (if passed).
189
- 2. `localStorage.delegation_group_id` — set by `<AppAuthProvider>` when
371
+ 2. `localStorage.delegation_group_id` — maintained by `<WalletProvider>` when
190
372
  delegation is active.
191
373
  3. `useAuth().user.id` — the authenticated user's own entity.
192
374
 
193
375
  Hosts that maintain their own actingAs model can pass `entityId`
194
376
  explicitly to override the auto-detection.
195
377
 
378
+ For a confirmed ownership edge `Group A → Group B`, the Wallet Center also
379
+ supports one ownership-derived hop. Start in the direct A session, then call:
380
+
381
+ ```ts
382
+ const {
383
+ delegationSession,
384
+ assumeOwnedGroup,
385
+ returnToParent,
386
+ } = useAuthContext();
387
+
388
+ // Select one active ECDSA USER key owned by the human operator. A group key
389
+ // is never eligible. Custodial keys execute through Automatic; external keys
390
+ // use the Manual signature drawer.
391
+ const userKeys = await keysService.getUserKeyPairs(currentUser.id);
392
+ const signer = userKeys.find(
393
+ (key) => ownedGroupUserSignerCustody(key, currentUser.id) !== null,
394
+ );
395
+ if (!signer) throw new Error('No eligible personal signing key');
396
+
397
+ // Mints a non-refreshable acting-as-B child and preserves direct A locally.
398
+ await assumeOwnedGroup(groupB, signer.id);
399
+
400
+ // Restores A only while its preserved access token is still live.
401
+ returnToParent();
402
+ ```
403
+
404
+ `KeyPair.account_signer_custody` is a non-secret, server-authoritative field:
405
+ `custodial` routes the selected human key through Automatic execution,
406
+ `external` routes it through the Manual wallet drawer, and `null` means the key
407
+ is not eligible for the account ECDSA65 signature profile. The SDK fails closed
408
+ when the field is missing and never infers sealed-key availability from
409
+ `provider_type`, `key_type`, or metadata.
410
+
411
+ `delegationSession` is a discriminated `direct | owned` lineage record. An
412
+ owned child stores the exact immutable `membershipEdge`, the selected
413
+ `signerKeyId`/address/custody, and `You → A → B` path. It rejects the removed
414
+ `authority_key_id` model. Owned children are capped at 15 minutes, cannot be
415
+ nested again, and never send `X-Refresh-Token`. The SDK automatically returns
416
+ to a still-live A when B expires; otherwise it clears delegation state.
417
+
418
+ In the concrete **Head Co → Subsidiary** UX:
419
+
420
+ 1. A Subsidiary Owner selects **Act as Subsidiary**, opens Subsidiary,
421
+ enters the Head Co group UUID under **Owning groups**, and reserves the
422
+ relationship.
423
+ 2. A Head Co Owner selects **Act as Head Co**, enters the
424
+ Subsidiary group UUID under **Relationship setup**, and advances the
425
+ immutable NFT mint with their ordinary USER key.
426
+ 3. After that operation settles, the Subsidiary Owner returns to
427
+ **Act as Subsidiary** and advances the same relationship again. This adds
428
+ the Head Co-owned NFT as a Subsidiary member; repeat after settlement until
429
+ the row is `active`.
430
+ 4. The Head Co operator selects **Act as Head Co**. Wallet Center now lists
431
+ Subsidiary under **Owned group accounts** and asks which personal key will
432
+ authorize the child. A custodial USER key selects Automatic; an external
433
+ USER key selects Manual.
434
+ 5. **Operate as Subsidiary** installs the child and displays
435
+ `You → Head Co → Subsidiary`.
436
+ 6. **Return to Head Co** restores the preserved direct parent; **Act as self**
437
+ clears the complete delegation lineage.
438
+
439
+ “Head Co operator” in the relationship ceremony means a live Head Co Owner,
440
+ not every Head Co member/admin and not the platform `UserRole::Operator` by
441
+ name. The narrowly defined staff fallback requires `ManageGroupOwners`.
442
+ Ordinary consumer Owners do not need that global permission. Head Co's members
443
+ do not become Subsidiary members. The on-chain authority is the NFT edge,
444
+ while each transaction remains attributable to the selected human EOA;
445
+ neither Head Co nor Subsidiary needs a YieldFabric-custodied group signing key.
446
+
447
+ Headless clients can discover B with
448
+ `groupsService.getOwnedGroups(groupAId, directAToken)` and perform the strict
449
+ exchange with `delegationService.assumeOwnedGroup(request, directAToken)`.
450
+ `request.signer_key_id` is mandatory and must name the operator's active USER
451
+ key. Relationship lifecycle calls are deliberately separate from raw owner
452
+ management:
453
+
454
+ ```ts
455
+ await groupsService.establishGroupOwner(childBId, parentAId, chainId, userBToken);
456
+ await groupsService.establishGroupOwner(childBId, parentAId, chainId, userAToken);
457
+ await groupsService.establishGroupOwner(childBId, parentAId, chainId, userBToken);
458
+ const status = await groupsService.getGroupOwners(childBId, chainId, userBToken);
459
+ ```
460
+
461
+ Those calls form an idempotent, multi-authority saga: B reserves, A mints, and
462
+ B adds the credential. POST/DELETE deliberately take the human's personal JWT;
463
+ the explicit parent/child IDs identify the stage, while any resulting MQ
464
+ message is still owned by the relevant group. Never substitute `add-owner`, a
465
+ group key, or a platform-held key for this flow.
466
+
467
+ Consumer apps should reuse the SDK panel instead of rebuilding the stage
468
+ machine or exposing the legacy raw-owner/group-key form:
469
+
470
+ ```tsx
471
+ <GroupOwnershipPanel
472
+ childGroup={subsidiary}
473
+ chainId={activeChainId}
474
+ accountReady={subsidiaryAccount.status === 'ready'}
475
+ currentRole={subsidiary.role}
476
+ activeDirectGroupId={delegationSession?.kind === 'direct'
477
+ ? delegationSession.currentGroupId
478
+ : null}
479
+ availableParentGroups={groups}
480
+ onChanged={() => refreshGroups()}
481
+ />
482
+ ```
483
+
484
+ The panel is fail-closed and stage-aware. Only a live child `owner` sees
485
+ mutations; `admin` is insufficient. It reserves and advances child membership
486
+ with POST, reconciles revocation with DELETE, disables setup until the child's
487
+ chain account is ready, and labels any parent groups the host already knows.
488
+ The parent mint stage remains in the direct parent context's
489
+ **Relationship setup** card because an A-only operator is intentionally unable
490
+ to list B's private relationship state.
491
+
492
+ The canonical business model, setup calls, exact JWT claims, lifetime rules,
493
+ revocation behaviour, and troubleshooting are documented in
494
+ [Group account ownership: Head Co → Subsidiary](../yieldfabric-auth/docs/api/guides/group-account-ownership.md).
495
+
196
496
  #### Signature requirement
197
497
 
198
498
  Accepting a payment or obligation enqueues an on-chain transaction
@@ -200,8 +500,9 @@ through the MQ. When the user is in **Manual** signature mode the
200
500
  SDK's signature drawer prompts them to sign before the executor
201
501
  fires. Consumers using this inbox **must mount
202
502
  `<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
503
+ component that wires the queue + drawer + toast together. Mount
504
+ `<WalletCenter />` separately when the app exposes the wallet shell.
505
+ `<SignatureDrawer />` by itself is a controlled
205
506
  inner that takes `message` / `onClose` / `userId` props; you don't
206
507
  mount it directly.
207
508
 
@@ -607,7 +908,7 @@ Import graph: `ui → react → providers → core`. Forbid backward edges.
607
908
  ## Build
608
909
 
609
910
  ```bash
610
- npm install
911
+ npm ci --ignore-scripts
611
912
  npm run build # one-shot — emits dist/index.{js,cjs,d.ts}
612
913
  npm run dev # watch mode — recompiles on save
613
914
  npm run typecheck # tsc --noEmit
@@ -623,9 +924,9 @@ npm run clean # rm -rf dist
623
924
  Both bundles are prepended with `"use client";` so Next.js App Router
624
925
  treats the SDK as client-side by default (the surface is hook + DOM-heavy).
625
926
 
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.
927
+ React peers and runtime dependencies declared in `package.json` stay external
928
+ to both bundles. Consumers install them through the `dependencies` /
929
+ `peerDependencies` declarations.
629
930
 
630
931
  ## Consuming from another monorepo package
631
932
 
@@ -658,23 +959,33 @@ If you're iterating on the SDK while running the consumer, leave
658
959
  `npm run dev` running in the SDK directory; the consumer's bundler will
659
960
  pick up changes on the next reload.
660
961
 
661
- ## Publishing
962
+ ## Controlled distribution
662
963
 
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:
964
+ Publication targets the npm registry using the existing `prepublishOnly` build
965
+ hook. `publishConfig.access: public` publishes the compiled artifact publicly.
966
+ The source repository retains its separate Git hosting access controls.
967
+ An authorized release requires an unused version and npm publish rights.
968
+
969
+ For YieldFabric's authorized preparation of a runtime artifact:
667
970
 
668
971
  ```bash
669
- # bump "version" first — re-publishing an existing version is rejected
670
- npm publish # prepublishOnly runs clean && build automatically
972
+ npm run build
973
+ npm pack --dry-run --ignore-scripts
671
974
  ```
672
975
 
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`.
976
+ The package allowlist contains only `dist/index.js`, `dist/index.cjs`,
977
+ `dist/index.d.ts`, `dist/index.d.cts`, `README.md`, `LICENSE.md`, and `NOTICE.md`
978
+ (npm also includes `package.json`). Source and declaration maps are disabled.
979
+ Built JavaScript and declarations still disclose implementation information;
980
+ they require an appropriate license and delivery decision.
981
+
982
+ Customer source review follows the source repository's `SOURCE_ACCESS.md` guide,
983
+ including a signed authorization and a controlled viewer. The source-review
984
+ agreement does not grant runtime or integration rights.
677
985
 
678
986
  ## License
679
987
 
680
- Proprietary. See repo root for terms.
988
+ [YieldFabric Proprietary Source Code Review and Non-Use Agreement](LICENSE.md).
989
+ Ownership remains with YieldFabric Technologies Ltd, subject to the
990
+ [third-party notices](NOTICE.md). No rights are granted merely by repository
991
+ access, possession of a package, or the technical examples in this README.