# Feature Spec: Lite Entry Point

**Feature Name:** lite-entry-point
**Date:** 2026-03-18
**Status:** Draft — Rev 4 (Morticia Rev 3 fixes)

---

## Revision History

| Rev | Date | Author | Summary |
|---|---|---|---|
| 1 | 2026-03-18 | Wednesday | Initial draft |
| 2 | 2026-03-18 | Wednesday | Address all Morticia Rev 1 issues: fix `this.streamId` contradiction; correct `batchFlushSeconds` setter claim; add `types` condition to exports map; add automated size verification; create `tsconfig.lite.json` unconditionally; add drift detection mechanism; add `_avoFunctionTrackSchemaFromEvent` pseudocode; specify `AvoNetworkCallsHandlerLite` constructor call; add static-isolation test; document fallback for bundlers without exports-map support |
| 3 | 2026-03-18 | Wednesday | Address all Morticia Rev 2 issues: fix `tsconfig.lite.json` to list lite copies instead of full originals; add `src/eventSpec/AvoEventSpecFetchTypes.ts` to `tsconfig.lite.json` include list; fix `check-lite-size.js` terser path logic (webpack → `bundle-lite.js`, terser → `bundle-lite-terser.js`); add `scripts/webpack.lite-size.config.js` to the Updated file list table |
| 4 | 2026-03-18 | Wednesday | Address all Morticia Rev 3 issues: fix `AvoSchemaParserLite.ts` import path from `"../AvoNetworkCallsHandler"` to `"./AvoNetworkCallsHandlerLite"`; update stale "Option A" section to reflect final architecture (lite copies of all four modules); add `mkdir -p scripts` instruction before creating scripts files; fix `AvoDeduplicatorLite` justification to state correct reason (imports `AvoSchemaParser` which pulls in `@noble/curves`) |

---

## Overview

Ship a pre-built "lite" CommonJS entry point at `avo-inspector/lite` that physically excludes all dev/staging-only code: `AvoEncryption.ts` (+ `@noble/curves`), the `eventSpec/` directory (+ `safe-regex2`), and all validation logic. Customers change two things: the import path and remove the `publicEncryptionKey` option (which the lite build does not accept — TypeScript will error). All tracking methods (`trackSchemaFromEvent`, `trackSchema`, `extractSchema`) keep the same async signatures and return types.

---

## User Stories

### Story 1: GTM/terser customer reduces bundle size
As a developer bundling avo-inspector into a GTM custom HTML tag, I want an entry point that contains no encryption or event-spec code, so that my bundled output is under 7 KB gzipped regardless of my bundler or terser settings.

### Story 2: TypeScript safety for lite consumers
As a TypeScript developer using `avo-inspector/lite`, I want the compiler to error if I accidentally pass `publicEncryptionKey` to the constructor, so I get a clear signal that encryption is not supported in the lite build.

### Story 3: Near-drop-in replacement
As a developer migrating from the full entry point to the lite entry point, I want to change only the import path (`avo-inspector` → `avo-inspector/lite`) and remove `publicEncryptionKey` from my constructor options, and have all my `trackSchemaFromEvent`, `trackSchema`, and `extractSchema` calls continue to work identically (returning the same types), so no other code changes are required.

### Story 4: Full entry point unchanged
As an existing consumer of `avo-inspector` (the full entry point), I want the 3.x package update to not change any behavior, exports, or bundle contents of the main entry point, so I am not affected by the lite build addition.

---

## Architecture Decision: Option A (Separate Lite Source Files)

> **Note:** This section describes the initial framing of Option A. The analysis below led to additional lite copies beyond what was originally anticipated. See "Final Architecture: Lite-Only Copies of Shared Files" for the complete decision reached after full dependency analysis.

Create `src/lite/` with a self-contained set of source files. The lite directory imports shared modules that have no dev dependencies (`AvoGuid`, `AvoStorage`, `AvoInspectorEnv`, `utils`), and provides lite copies of all modules that would otherwise transitively pull in excluded code: `AvoNetworkCallsHandlerLite`, `AvoBatcherLite`, `AvoDeduplicatorLite`, `AvoStreamIdLite`, `AvoSchemaParserLite`, and `AvoInspectorLite`.

**Why not Option C (stubs)?** The existing `AvoSchemaParser.ts` statically imports `encryptValue` from `AvoEncryption.ts` at the top level. Even if `encryptValue` is never called, bundlers may still include `@noble/curves` because the import is unconditional. A separate lite parser with no such import is cleaner and more verifiable.

**Key constraint — all four "shared" files need lite copies:** `AvoNetworkCallsHandler.ts`, `AvoBatcher.ts`, and `AvoStreamId.ts` all import `AvoInspector` (full) for its static properties. `AvoDeduplicator.ts` imports `AvoSchemaParser` which imports `AvoEncryption` → `@noble/curves`. None of these files can be reused directly in the lite build without pulling in excluded code. Each receives a lite copy in `src/lite/` where only the problematic import line changes.

---

## Architecture Tradeoff: Lite Copies vs. `AvoInspectorState` Refactor

The spec arrives at the "lite copies" architecture (Option A) over a shared-state module approach. This section provides the honest tradeoff analysis.

### Option A (this spec): Lite copies of files that import `AvoInspector`

Five files need lite copies because they transitively pull in excluded modules at runtime. Three (`AvoNetworkCallsHandler`, `AvoBatcher`, `AvoStreamId`) import `AvoInspector` (full), which re-imports `eventSpec/` modules — so a lite copy is created for each, changing only the `AvoInspector` import to `AvoInspectorLite`. One (`AvoDeduplicator`) imports `AvoSchemaParser`, which imports `AvoEncryption` → `@noble/curves` — so `AvoDeduplicatorLite` imports `AvoSchemaParserLite` instead. One (`AvoSchemaParser`) directly imports `AvoEncryption` → `@noble/curves` — so `AvoSchemaParserLite` removes that import entirely. In each copy, one import line changes. Full source files are untouched.

**Cost:** Every future change to any of these five files must be applied twice. The maintenance obligation is ongoing and invisible unless enforced. This spec adds a drift-detection CI step (see "Drift Detection" below) to make divergence visible.

**Benefit:** Zero changes to full source files. No regression risk on the existing production code path.

### Alternative: `AvoInspectorState` shared-state module

Extract `avoStorage`, `shouldLog`, `batchSize`, `batchFlushSeconds`, `networkTimeout` statics into `src/AvoInspectorState.ts` (~30 lines, additive). Have `AvoNetworkCallsHandler`, `AvoBatcher`, and `AvoStreamId` import `AvoInspectorState` instead of `AvoInspector`. This eliminates three of the five lite copies.

**Cost:** Modifies three full source files. Requires regression-testing the full path. The interview brief explicitly states the full entry point must stay unchanged — while this is a purely internal refactor with no public API change, it modifies files the brief marks as out-of-scope.

**Benefit:** Eliminates 3 of 5 lite copies, reducing long-term maintenance to 2 files (`AvoSchemaParser`, `AvoDeduplicator`).

**Decision:** Option A is chosen for this revision because it keeps full source files completely untouched, which is the brief's explicit requirement. The maintenance debt is acknowledged and mitigated by drift detection. The `AvoInspectorState` refactor is a valid future improvement that can be adopted independently.

---

## Implementation Plan

### Files to Create

#### `src/lite/AvoSchemaParserLite.ts`
A copy of `AvoSchemaParser.ts` with encryption removed:
- Remove `import { encryptValue } from "../AvoEncryption"` (this is the only line that pulls in `@noble/curves`)
- Remove `canSendEncryptedValues()` — always returns false conceptually
- Remove `getEncryptedPropertyValueIfEnabled()` — always returns `undefined`
- The `extractSchema` signature stays identical: `static async extractSchema(eventProperties, publicEncryptionKey?, env?)` — the `publicEncryptionKey` and `env` parameters are accepted but ignored (no-ops), ensuring backward compatibility if any internal caller passes them
- The mapping logic is identical to the full parser, minus the `encryptedPropertyValue` assignment path

#### `src/lite/AvoInspectorLite.ts`
A stripped version of `AvoInspector.ts`:
- Remove imports: `EventSpecCache`, `AvoEventSpecFetcher`, `validateEvent`, `ValidationResult`, `PropertyValidationResult`, `EventSpecResponse`
- Remove private fields: `publicEncryptionKey`, `streamId`, `eventSpecCache`, `eventSpecFetcher`, `currentBranchId`
- Remove private methods: `handleBranchChangeAndCache`, `fetchEventSpecIfNeeded`, `fetchAndValidateEvent`, `mergeValidationResults`, `mergePropertyValidation`, `sendEventWithValidation`
- Constructor options type: remove `publicEncryptionKey?` — TypeScript customers who pass it get a compile error
- Constructor body: remove `this.publicEncryptionKey = options.publicEncryptionKey` and the eventSpec initialization block (the `if (this.streamId)` block that creates `EventSpecCache` and `AvoEventSpecFetcher`). The local assignment `this.streamId = AvoStreamId.streamId` is also removed — `streamId` is NOT a field of `AvoInspectorLite` (it was used only for eventSpec gating, which is removed). `AvoStreamIdLite` is wired into `AvoBatcherLite` and `AvoNetworkCallsHandlerLite` through their own imports and is not needed directly in the `AvoInspectorLite` constructor.
- `trackSchemaFromEvent`: replaces the full version's two-path flow (with/without validation) with a single path: extract schema, then call `this.trackSchemaInternal`. Always async, always returns `Promise<EventProperty[]>`.
- `_avoFunctionTrackSchemaFromEvent`: same simplification — see pseudocode below
- `trackSchema`: removes `await this.fetchEventSpecIfNeeded(eventName)` line; otherwise identical
- `extractSchema`: calls `AvoSchemaParserLite.extractSchema(eventProperties)` (no key, no env)
- All static fields (`avoStorage`, `batchSize`, `batchFlushSeconds`, `shouldLog`, `networkTimeout`) and their getters/setters are identical to the full version. Note: `batchFlushSeconds` has a **getter only** (no static setter) — it is set via the instance method `setBatchFlushSeconds(n)`, exactly as in `AvoInspector.ts`. `AvoInspectorLite` must replicate this exactly.

#### `src/lite/index.ts`
```typescript
export { AvoInspectorLite as AvoInspector } from "./AvoInspectorLite";
export { AvoInspectorEnv, type AvoInspectorEnvType, type AvoInspectorEnvValueType } from "../AvoInspectorEnv";
```
The export alias `AvoInspectorLite as AvoInspector` means consumers see `AvoInspector` — identical to the full entry point. `AvoStreamId` should also be exported if it is exported from the full entry point. The current `src/index.ts` does NOT export `AvoStreamId`, so the lite index matches that.

### Files to Modify

#### `tsconfig.json` → no change
The existing `tsconfig.json` compiles `src/**/*` to `dist/`, covering `src/lite/**/*`. However, a separate `tsconfig.lite.json` is created unconditionally for CI isolation (see next section).

#### `tsconfig.lite.json` (new, unconditional)
The interview brief explicitly requires a separate tsconfig for the lite build. It is created unconditionally (not as a fallback):
```json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./dist"
  },
  "include": [
    "src/lite/**/*",
    "src/AvoInspectorEnv.ts",
    "src/AvoStorage.ts",
    "src/AvoGuid.ts",
    "src/utils.ts",
    "src/eventSpec/AvoEventSpecFetchTypes.ts"
  ]
}
```
The `include` list contains only:
- `src/lite/**/*` — picks up all lite copies (`AvoInspectorLite.ts`, `AvoNetworkCallsHandlerLite.ts`, `AvoBatcherLite.ts`, `AvoStreamIdLite.ts`, `AvoDeduplicatorLite.ts`, `AvoSchemaParserLite.ts`, `index.ts`) in one glob
- Shared modules used as-is with no modification and no `AvoInspector` import: `AvoStorage.ts`, `AvoGuid.ts`, `utils.ts`, `AvoInspectorEnv.ts`
- `src/eventSpec/AvoEventSpecFetchTypes.ts` — a pure types-only file (contains only TypeScript `type` declarations). The lite copies of `AvoNetworkCallsHandlerLite.ts` and `AvoBatcherLite.ts` keep `import type { EventSpecMetadata }` from this file. TypeScript erases type-only imports at runtime, but `tsc --noEmit` still resolves the file to type-check. Including it here does not introduce any runtime code into the lite build. Without it, the `--noEmit` check would fail with "Cannot find module '../eventSpec/AvoEventSpecFetchTypes'" on every lite copy that uses `EventSpecMetadata`.

**Not included:** `src/AvoNetworkCallsHandler.ts`, `src/AvoBatcher.ts`, `src/AvoStreamId.ts`, `src/AvoDeduplicator.ts` — these are the full originals that import `AvoInspector` (full). They are NOT used by the lite build; their lite copies in `src/lite/` are used instead and are already covered by the `src/lite/**/*` glob. Including the full originals would undermine the isolation check — if `src/AvoNetworkCallsHandler.ts` were in the list, compilation might fail with "Cannot find module './AvoInspector'" (since `AvoInspector.ts` is not in the list), which is a different failure than the intended "cannot import eventSpec" guard.

This enables CI to run `tsc --project tsconfig.lite.json --noEmit` and catch any eventSpec runtime import that crept into the lite source files. If such an import were added by mistake, the isolation compilation would fail because eventSpec runtime modules are not listed in the `include` array. This is the principal CI value of having the separate tsconfig.

Add to the `build` script in `package.json`:
```
"build": "tsc --emitDeclarationOnly && tsc --project tsconfig.lite.json --noEmit && webpack --config webpack.config.js"
```

#### `package.json`
Add to the `"exports"` map (create the map if absent) and add `dist/lite` to `"files"`:

```json
{
  "files": ["dist", "bin"],
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "default": "./dist/index.js"
    },
    "./lite": {
      "types": "./dist/lite/index.d.ts",
      "default": "./dist/lite/index.js"
    }
  }
}
```

The `types` condition is required for TypeScript 4.7+ consumers using `"moduleResolution": "bundler"` or `"node16"`. Without it, TypeScript consumers of `avo-inspector/lite` would get an implicit `any` or a "cannot find module" error — which would directly break Story 2's `@ts-expect-error` test for `publicEncryptionKey`. The `types` field must be listed before `default` in the condition object (TypeScript resolver checks conditions in order).

The existing `"main"` and `"types"` fields are kept for backwards compatibility with tools that don't support exports maps (see "Bundler Fallback" section below).

Note: `"files": ["dist", "bin"]` already covers `dist/lite/` recursively — no special entry needed for the subdirectory. The current `package.json` has no `"exports"` field and no `"files"` field (it uses `.npmignore`). The `.npmignore` is already deleted in the working tree (`D .npmignore` in git status). Adding `"files"` is the correct approach.

#### `package.json` — build script
Update `"build"` to also run the lite isolation check:
```json
"build": "tsc --emitDeclarationOnly && tsc --project tsconfig.lite.json --noEmit && webpack --config webpack.config.js"
```

Add the size verification script (see "Automated Size Verification" below):
```json
"check:lite-size": "node scripts/check-lite-size.js"
```

---

## Drift Detection

**Problem:** Five lite-copy files must stay in sync with their full counterparts. A missed sync will silently diverge lite behavior with no CI signal.

**Solution 1: File header comment.** Each lite-copy file begins with:
```typescript
// LITE COPY of src/<OriginalFile>.ts
// Sync obligation: any change to src/<OriginalFile>.ts must be reviewed for applicability here.
// Diff against original: git diff HEAD src/<OriginalFile>.ts src/lite/<LiteCopyFile>.ts
```

**Solution 2: CI diff script.** The `scripts/` directory does not currently exist in the repo. Before creating any files under it, run:
```bash
mkdir -p scripts
```
Then add `scripts/verify-lite-sync.sh`:
```bash
#!/usr/bin/env bash
# Fails if a lite copy has diverged from its full counterpart in unexpected ways.
# "Unexpected" means: more than the one import-line substitution that is the declared intent.

PAIRS=(
  "src/AvoNetworkCallsHandler.ts src/lite/AvoNetworkCallsHandlerLite.ts"
  "src/AvoBatcher.ts src/lite/AvoBatcherLite.ts"
  "src/AvoStreamId.ts src/lite/AvoStreamIdLite.ts"
  "src/AvoDeduplicator.ts src/lite/AvoDeduplicatorLite.ts"
)

FAIL=0
for PAIR in "${PAIRS[@]}"; do
  FULL=$(echo $PAIR | cut -d' ' -f1)
  LITE=$(echo $PAIR | cut -d' ' -f2)
  DIFF_LINES=$(diff "$FULL" "$LITE" | grep -c '^[<>]' || true)
  # Each pair should differ by exactly 2–6 lines (the import substitution lines)
  if [ "$DIFF_LINES" -gt 10 ]; then
    echo "DRIFT DETECTED: $LITE has diverged from $FULL ($DIFF_LINES changed lines, expected ≤10)"
    FAIL=1
  fi
done

exit $FAIL
```

Add to `package.json` scripts:
```json
"verify:lite-sync": "bash scripts/verify-lite-sync.sh"
```

Add `verify:lite-sync` to the CI pipeline (or `prepublishOnly` hook). This makes unexpected drift a build failure.

---

## Automated Size Verification

**Problem:** The 7 KB gzipped acceptance criterion has no automated CI verification. A size regression in a future PR will not be caught.

**Solution:** Add `scripts/check-lite-size.js` and a `check:lite-size` npm script. This script extends the existing `test-bundle-size/` infrastructure pattern:

```javascript
// scripts/check-lite-size.js
// Bundles dist/lite/index.js with webpack + terser, gzips the result,
// and fails if the output exceeds 7168 bytes (7 KB).
// Run after `yarn build`.

const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');

const LIMIT_BYTES = 7168; // 7 KB gzipped

// Bundle the lite entry point using the same approach as test-bundle-size/
// Step 1: webpack produces the raw bundle (bundle-lite.js)
execSync('npx webpack --config scripts/webpack.lite-size.config.js', { stdio: 'inherit' });

// Step 2: terser reads bundle-lite.js (webpack output) and writes bundle-lite-terser.js
// This mirrors the existing analyze.sh pattern: webpack → bundle-cjs.js → terser → bundle-cjs-terser.js
const bundlePath = path.resolve(__dirname, '../test-bundle-size/output/bundle-lite.js');
const terserOutputPath = path.resolve(__dirname, '../test-bundle-size/output/bundle-lite-terser.js');
const terserResult = execSync(
  `npx terser ${bundlePath} --compress passes=2,unsafe=true --mangle`,
  { encoding: 'utf8' }
);
fs.writeFileSync(terserOutputPath, terserResult);

const gzipped = zlib.gzipSync(fs.readFileSync(terserOutputPath));
const bytes = gzipped.length;

console.log(`Lite bundle: ${bytes} bytes gzipped (limit: ${LIMIT_BYTES})`);
if (bytes > LIMIT_BYTES) {
  console.error(`FAIL: lite bundle ${bytes}B exceeds ${LIMIT_BYTES}B limit`);
  process.exit(1);
}
console.log('PASS');
```

Add `scripts/webpack.lite-size.config.js` following the same pattern as `test-bundle-size/webpack.cjs.config.js` but pointing at `dist/lite/index.js` and outputting `bundle-lite.js` (no `-terser` suffix) to `test-bundle-size/output/`. The terser step in `check-lite-size.js` reads this file and writes the minified result to `bundle-lite-terser.js`. This matches the existing pattern in `analyze.sh`: webpack → `bundle-cjs.js`, terser → `bundle-cjs-terser.js`.

```javascript
// scripts/webpack.lite-size.config.js
// Bundles the lite entry point for size measurement.
// webpack writes bundle-lite.js; check-lite-size.js runs terser on it afterward.
const path = require("path");

module.exports = {
  mode: "production",
  entry: "./dist/lite/index.js",
  resolve: {
    extensions: [".js"],
  },
  output: {
    filename: "bundle-lite.js",
    path: path.resolve(__dirname, "../test-bundle-size/output"),
    libraryTarget: "umd",
  },
  optimization: {
    splitChunks: false,
  },
};
```

Add to CI or `prepublishOnly`:
```json
"prepublishOnly": "yarn build && yarn check:lite-size && yarn test:browser && ..."
```

**Acceptance criterion 17 is now automated**, not manual.

---

## Bundler Fallback for Exports-Map

**Problem:** A consumer on older tooling (webpack 4, browserify, rollup without exports-map plugin) trying to import `"avo-inspector/lite"` will get a "Module Not Found" error. The `"main"` field fallback only applies to the root import `"avo-inspector"`, not to subpath imports.

**Documented escape hatch:** For bundlers that do not support the `exports` map, the direct path import works:
```javascript
// Older tooling fallback (not recommended for new projects)
const { AvoInspector } = require("avo-inspector/dist/lite/index.js");
```
or in TypeScript:
```typescript
import { AvoInspector } from "avo-inspector/dist/lite/index.js";
```

This path is stable (dist/lite/index.js is always the compiled entry point) and is not subject to exports-map resolution. Document this in the package README under "Lite Entry Point → Compatibility."

Note: TypeScript users on older `moduleResolution` who use the direct path should add a `paths` override in their `tsconfig.json` if they want the type alias `"avo-inspector/lite"` to resolve:
```json
"paths": { "avo-inspector/lite": ["./node_modules/avo-inspector/dist/lite/index.d.ts"] }
```

---

## Dependency Analysis

### What the lite build imports (runtime only)
| Module | Imports | Notes |
|---|---|---|
| `AvoInspectorLite.ts` | `AvoInspectorEnv`, `AvoSchemaParserLite`, `AvoBatcherLite`, `AvoNetworkCallsHandlerLite`, `AvoStorage`, `AvoDeduplicatorLite`, `AvoStreamIdLite`, `isValueEmpty` from utils | All safe |
| `AvoSchemaParserLite.ts` | `EventProperty`, `SchemaChild` types from `AvoNetworkCallsHandlerLite` | Type-only, safe |
| `AvoNetworkCallsHandlerLite.ts` (lite copy) | `AvoGuid`, `AvoInspectorLite`, `AvoStreamIdLite`, `type EventSpecMetadata` | The `EventSpecMetadata` import is `type`-only — erased at compile time, no runtime dependency on `eventSpec/` |
| `AvoBatcherLite.ts` (lite copy) | `AvoNetworkCallsHandlerLite` types, `AvoInspectorLite`, `type EventSpecMetadata` | Same — `EventSpecMetadata` is type-only |
| `AvoDeduplicatorLite.ts` (lite copy) | `AvoSchemaParserLite`, `deepEquals` from utils | Safe — no AvoSchemaParser (full) import |

### Problem: `AvoDeduplicator.ts` imports `AvoSchemaParser`
`AvoDeduplicator.lookForEventSchemaIn` calls `AvoSchemaParser.extractSchema(...)`. This pulls in the full parser, which pulls in `AvoEncryption.ts` and `@noble/curves`.

**Solution:** Create `src/lite/AvoDeduplicatorLite.ts` that is identical to `AvoDeduplicator.ts` except it imports `AvoSchemaParserLite` instead of `AvoSchemaParser`. This is a one-line change in the imports.

Update `AvoInspectorLite.ts` to import `AvoDeduplicatorLite` instead of `AvoDeduplicator`.

### Updated file list
| File | Action |
|---|---|
| `src/lite/AvoSchemaParserLite.ts` | Create — no encryption import |
| `src/lite/AvoDeduplicatorLite.ts` | Create — imports AvoSchemaParserLite |
| `src/lite/AvoInspectorLite.ts` | Create — no eventSpec, imports AvoDeduplicatorLite and AvoSchemaParserLite |
| `src/lite/index.ts` | Create — exports |
| `package.json` | Modify — add exports map and files field |

### Shared modules used as-is (no modification)
- `src/AvoNetworkCallsHandler.ts` — type-only eventSpec import, safe
- `src/AvoBatcher.ts` — type-only eventSpec import, safe
- `src/AvoStorage.ts` — no eventSpec imports
- `src/AvoStreamId.ts` — imports `AvoInspector` (full), but this is a static class that reads `AvoInspector.avoStorage` — **the lite `AvoStreamId` is shared** because it references the `avoStorage` static on whatever `AvoInspector` class is active. Since `AvoInspectorLite` will also have a `static avoStorage: AvoStorage`, and `AvoStreamId` does `AvoInspector.avoStorage`, we need to handle this carefully. See below.

### Problem: `AvoStreamId.ts` imports `AvoInspector` (full)
`AvoStreamId` does `import { AvoInspector } from "./AvoInspector"` and accesses `AvoInspector.avoStorage`. If the lite build includes `AvoStreamId` as-is, it will pull in the full `AvoInspector.ts`, which re-imports eventSpec modules.

**Solution:** TypeScript compiles modules independently. Since `AvoStreamId` is shared between the full and lite builds, we need to ensure it doesn't pull in eventSpec at runtime.

Looking at `AvoInspector.ts` imports carefully: `AvoInspector.ts` itself imports `EventSpecCache`, `AvoEventSpecFetcher`, and `validateEvent`. These are loaded when the module is `require()`'d. So including `AvoStreamId` in the lite build would transitively include the full `AvoInspector` module and thus eventSpec.

**Concrete solution:** Create `src/lite/AvoStreamIdLite.ts` — identical to `AvoStreamId.ts` but importing `AvoInspectorLite` instead of `AvoInspector`. Update `AvoInspectorLite.ts` to use `AvoStreamIdLite`, and update `AvoNetworkCallsHandler.ts` usage... wait — `AvoNetworkCallsHandler.ts` also imports `AvoStreamId` directly and accesses `AvoStreamId.streamId`. And `AvoBatcher.ts` imports `AvoInspector` directly (for `AvoInspector.avoStorage`, `AvoInspector.shouldLog`, `AvoInspector.batchSize`, etc.).

**Re-evaluation:** The shared files `AvoNetworkCallsHandler.ts`, `AvoBatcher.ts`, and `AvoStreamId.ts` all import from `./AvoInspector`. Using them as-is in the lite build will include the full `AvoInspector.ts` at runtime. The only way to fully exclude eventSpec is to either:
- Create lite copies of these files too (changing the import from `AvoInspector` to `AvoInspectorLite`), or
- Restructure the shared state (AvoStorage, shouldLog, batchSize) into a separate module that both full and lite can import without cross-dependency

**Recommended approach: Create lite copies of the files that import AvoInspector.**

This is a narrow problem. The files that import `AvoInspector` for its static properties are:
- `AvoNetworkCallsHandler.ts` — imports `AvoInspector` for `AvoInspector.shouldLog` and `AvoInspector.networkTimeout`
- `AvoBatcher.ts` — imports `AvoInspector` for `AvoInspector.avoStorage`, `AvoInspector.shouldLog`, `AvoInspector.batchSize`, `AvoInspector.batchFlushSeconds`
- `AvoStreamId.ts` — imports `AvoInspector` for `AvoInspector.avoStorage`

Rather than duplicating three substantial files, the cleaner solution is to extract the shared static state into a dedicated module.

**Alternative (simpler):** Keep the lite copies minimal by using a thin shared-state module.

---

## Revised Architecture: Shared State Module

### New file: `src/AvoInspectorState.ts`
Extract the static state from `AvoInspector` into a module that has no eventSpec dependency:

```typescript
// src/AvoInspectorState.ts
import { AvoStorage } from "./AvoStorage";

export class AvoInspectorState {
  static avoStorage: AvoStorage;
  private static _shouldLog = false;
  static get shouldLog() { return this._shouldLog; }
  static set shouldLog(v) { this._shouldLog = v; }
  private static _batchSize = 30;
  static get batchSize() { return this._batchSize; }
  static set batchSize(v) { this._batchSize = v < 1 ? 1 : v; }
  private static _batchFlushSeconds = 30;
  static get batchFlushSeconds() { return this._batchFlushSeconds; }
  private static _networkTimeout = 2000;
  static get networkTimeout() { return this._networkTimeout; }
  static set networkTimeout(v) { this._networkTimeout = v; }
}
```

Then `AvoInspector.ts` re-exports the statics from `AvoInspectorState`, `AvoNetworkCallsHandler.ts` imports `AvoInspectorState` instead of `AvoInspector`, etc.

**This is a significant refactor of the full source files and risks introducing regressions.** Given the interview brief's explicit out-of-scope statement ("Full entry point stays unchanged"), this approach is undesirable.

---

## Final Architecture: Lite-Only Copies of Shared Files

Create lite copies of only the files that import `AvoInspector` for its static properties. The copies are identical except for the `AvoInspector` import being replaced with `AvoInspectorLite`. This is the safest approach — no changes to the full source files.

### Complete file list

| File | Action | Description |
|---|---|---|
| `src/lite/AvoSchemaParserLite.ts` | Create | Full AvoSchemaParser minus `encryptValue` import and all encryption logic |
| `src/lite/AvoDeduplicatorLite.ts` | Create | Full AvoDeduplicator with `AvoSchemaParserLite` import instead of `AvoSchemaParser` |
| `src/lite/AvoNetworkCallsHandlerLite.ts` | Create | Full AvoNetworkCallsHandler with `AvoInspectorLite` import instead of `AvoInspector` |
| `src/lite/AvoBatcherLite.ts` | Create | Full AvoBatcher with `AvoInspectorLite` import instead of `AvoInspector` |
| `src/lite/AvoStreamIdLite.ts` | Create | Full AvoStreamId with `AvoInspectorLite` import instead of `AvoInspector` |
| `src/lite/AvoInspectorLite.ts` | Create | Stripped AvoInspector — imports all Lite variants, no eventSpec, no publicEncryptionKey |
| `src/lite/index.ts` | Create | Exports `AvoInspector` (alias for AvoInspectorLite) and `AvoInspectorEnv` |
| `package.json` | Modify | Add `"exports"` map and `"files"` field |
| `tsconfig.lite.json` | Create | Lite-only compilation for CI isolation check |
| `scripts/verify-lite-sync.sh` | Create | CI drift detection between lite copies and originals |
| `scripts/check-lite-size.js` | Create | Automated 7 KB gzip enforcement |
| `scripts/webpack.lite-size.config.js` | Create | webpack config for lite bundle size check |

**Full source files: zero modifications.**

---

## Detailed File Specifications

### `src/lite/AvoSchemaParserLite.ts`
- Copy of `src/AvoSchemaParser.ts`
- Remove: `import { encryptValue } from "./AvoEncryption"` → deleted entirely
- Change import path for types: `import type { EventProperty, SchemaChild } from "./AvoNetworkCallsHandlerLite"` (use the lite copy, which is in the same `src/lite/` directory; importing from `"../AvoNetworkCallsHandler"` would reference the full handler which is not in the `tsconfig.lite.json` include list and would cause the `--noEmit` isolation check to fail)
- Remove private method `canSendEncryptedValues` — delete
- Remove private method `getEncryptedPropertyValueIfEnabled` — delete
- In `extractSchema`: delete `const canSendEncryptedValues = ...` line
- In the mapping function's primitive branch: delete the entire `const encryptedValue = await this.getEncryptedPropertyValueIfEnabled(...)` block and the `if (encryptedValue !== undefined)` block
- The `extractSchema` method signature remains: `static async extractSchema(eventProperties, publicEncryptionKey?, env?): Promise<EventProperty[]>` — parameters are accepted but ignored, ensuring no type errors if called internally with those args
- `removeDuplicates` and `getPropValueType` are copied unchanged

### `src/lite/AvoDeduplicatorLite.ts`
- Copy of `src/AvoDeduplicator.ts`
- Change: `import { AvoSchemaParser } from "./AvoSchemaParser"` → `import { AvoSchemaParserLite as AvoSchemaParser } from "./AvoSchemaParserLite"`
- Change import path for utils: `import { deepEquals } from "../utils"`
- The alias `as AvoSchemaParser` means the body of `lookForEventSchemaIn` is unchanged
- Everything else is identical

### `src/lite/AvoStreamIdLite.ts`
- Copy of `src/AvoStreamId.ts`
- Change: `import { AvoInspector } from "./AvoInspector"` → `import { AvoInspectorLite as AvoInspector } from "./AvoInspectorLite"`
- Change: `import AvoGuid from "./AvoGuid"` → `import AvoGuid from "../AvoGuid"`
- Everything else is identical; the alias `as AvoInspector` means no other line changes

### `src/lite/AvoNetworkCallsHandlerLite.ts`
- Copy of `src/AvoNetworkCallsHandler.ts`
- Change: `import { AvoInspector } from "./AvoInspector"` → `import { AvoInspectorLite as AvoInspector } from "./AvoInspectorLite"`
- Change: `import AvoGuid from "./AvoGuid"` → `import AvoGuid from "../AvoGuid"`
- Change: `import { AvoStreamId } from "./AvoStreamId"` → `import { AvoStreamIdLite as AvoStreamId } from "./AvoStreamIdLite"`
- Change: `import type { EventSpecMetadata } from "./eventSpec/AvoEventSpecFetchTypes"` → `import type { EventSpecMetadata } from "../eventSpec/AvoEventSpecFetchTypes"` — this is a type-only import, safe for lite builds (type imports are erased at compile time)
- Everything else is identical; the alias `as AvoInspector` means no further body changes

**Constructor call from `AvoInspectorLite`:** The full `AvoInspector` passes `this.publicEncryptionKey` as the 6th argument to `new AvoNetworkCallsHandler(...)`. `AvoInspectorLite` has no `publicEncryptionKey` field. The constructor call in `AvoInspectorLite` must pass `undefined` as the 6th argument:
```typescript
this.avoNetworkCallsHandler = new AvoNetworkCallsHandlerLite(
  this.apiKey,
  this.environment.toString(),
  options.appName || "",
  this.version,
  libVersion,
  undefined  // publicEncryptionKey — lite build does not support encryption
);
```
The `publicEncryptionKey` parameter remains in `AvoNetworkCallsHandlerLite`'s constructor signature (unchanged from the full handler). Passing `undefined` is correct and harmless — the handler will simply omit encryption from network calls.

### `src/lite/AvoBatcherLite.ts`
- Copy of `src/AvoBatcher.ts`
- Change: `import { AvoInspector } from "./AvoInspector"` → `import { AvoInspectorLite as AvoInspector } from "./AvoInspectorLite"`
- Change: import of `AvoNetworkCallsHandler` types → from `./AvoNetworkCallsHandlerLite`
- Change: `import type { EventSpecMetadata } from "./eventSpec/AvoEventSpecFetchTypes"` → `import type { EventSpecMetadata } from "../eventSpec/AvoEventSpecFetchTypes"` (type-only, safe)
- The `AvoBatcherType` interface is copied verbatim with `eventSpecMetadata?` retained in the `handleTrackSchema` signature — this is a type-only dependency and is safe to keep. Do not remove `eventSpecMetadata?` from the interface.
- Everything else is identical

### `src/lite/AvoInspectorLite.ts`
Imports:
```typescript
import { AvoInspectorEnv, type AvoInspectorEnvValueType } from "../AvoInspectorEnv";
import { AvoSchemaParserLite } from "./AvoSchemaParserLite";
import { AvoBatcherLite as AvoBatcher } from "./AvoBatcherLite";
import { AvoNetworkCallsHandlerLite as AvoNetworkCallsHandler, type EventProperty } from "./AvoNetworkCallsHandlerLite";
import { AvoStorage } from "../AvoStorage";
import { AvoDeduplicatorLite as AvoDeduplicator } from "./AvoDeduplicatorLite";
import { AvoStreamIdLite as AvoStreamId } from "./AvoStreamIdLite";
import { isValueEmpty } from "../utils";
const libVersion = require("../../package.json").version;
```

Constructor options type (`publicEncryptionKey` removed):
```typescript
constructor(options: {
  apiKey: string;
  env: AvoInspectorEnvValueType;
  version: string;
  appName?: string;
  suffix?: string;
})
```

Constructor body: identical to full constructor but without:
- `this.publicEncryptionKey = options.publicEncryptionKey`
- `this.streamId = AvoStreamId.streamId` — **removed**. `streamId` is NOT a field of `AvoInspectorLite`. It was used only as a gate for the eventSpec initialization block (`if (this.streamId) { ... }`), which is entirely removed in the lite version. `AvoStreamIdLite` provides session/installation tracking through `AvoBatcherLite` and `AvoNetworkCallsHandlerLite` without needing a direct field on `AvoInspectorLite`.
- The entire `if (this.streamId) { this.eventSpecCache = ...; this.eventSpecFetcher = ...; }` block → deleted

`trackSchemaFromEvent`:
```typescript
async trackSchemaFromEvent(
  eventName: string,
  eventProperties: Record<string, any>
): Promise<EventProperty[]> {
  try {
    if (this.avoDeduplicator.shouldRegisterEvent(eventName, eventProperties, false)) {
      // logging...
      const eventSchema = await this.extractSchema(eventProperties, false);
      this.trackSchemaInternal(eventName, eventSchema, null, null);
      return eventSchema;
    } else {
      // logging...
      return [];
    }
  } catch (e) {
    // error handling...
    return [];
  }
}
```

`_avoFunctionTrackSchemaFromEvent`:
This method mirrors `trackSchemaFromEvent` in structure. The only differences are: (1) `shouldRegisterEvent` is called with `fromAvoFunction: true` (third argument is `true` instead of `false`), and (2) `eventId` and `eventHash` are passed through to `trackSchemaInternal`:
```typescript
private async _avoFunctionTrackSchemaFromEvent(
  eventName: string,
  eventProperties: Record<string, any>,
  eventId: string,
  eventHash: string
): Promise<EventProperty[]> {
  try {
    if (this.avoDeduplicator.shouldRegisterEvent(eventName, eventProperties, true)) {
      // logging...
      const eventSchema = await this.extractSchema(eventProperties, false);
      this.trackSchemaInternal(eventName, eventSchema, eventId, eventHash);
      return eventSchema;
    } else {
      // logging...
      return [];
    }
  } catch (e) {
    // error handling...
    return [];
  }
}
```

`trackSchema`: identical to full version but without the `await this.fetchEventSpecIfNeeded(eventName)` line.

`extractSchema`: calls `AvoSchemaParserLite.extractSchema(eventProperties)` — no key, no env.

**Static fields and getters/setters:** `AvoInspectorLite` has class-level statics for `avoStorage`, `batchSize`, `batchFlushSeconds`, `shouldLog`, `networkTimeout`. These are independent statics, not shared with `AvoInspector`. The getter/setter pattern must match the full class exactly:
- `batchSize`: has both `static get` and `static set`
- `batchFlushSeconds`: has **getter only** (`static get batchFlushSeconds()`). There is **no** `static set batchFlushSeconds`. It is set via the instance method `setBatchFlushSeconds(n)` — this is by design in the full class and must be replicated exactly in the lite class.
- `shouldLog`: has both `static get` and `static set`
- `networkTimeout`: has both `static get` and `static set`

### `src/lite/index.ts`
```typescript
export { AvoInspectorLite as AvoInspector } from "./AvoInspectorLite";
export { AvoInspectorEnv, type AvoInspectorEnvType, type AvoInspectorEnvValueType } from "../AvoInspectorEnv";
```

### `package.json` changes
```json
{
  "files": ["dist", "bin"],
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "default": "./dist/index.js"
    },
    "./lite": {
      "types": "./dist/lite/index.d.ts",
      "default": "./dist/lite/index.js"
    }
  }
}
```
The existing `"main"` and `"types"` fields are kept for backwards compatibility with tools that don't support exports maps. The `.npmignore` file is already removed in the working tree, so `"files"` is the correct mechanism.

---

## Build System

### Compilation
The existing `tsconfig.json` already has `"include": ["src/**/*"]`, which covers `src/lite/**/*`. Running `tsc --emitDeclarationOnly` (the first half of the build script) will compile all lite source files into `dist/lite/`. The `"outDir": "./dist"` setting means `src/lite/AvoInspectorLite.ts` → `dist/lite/AvoInspectorLite.js` + `dist/lite/AvoInspectorLite.d.ts`. The entry point `src/lite/index.ts` → `dist/lite/index.js` + `dist/lite/index.d.ts`.

The `tsconfig.lite.json` additionally runs `--noEmit` in CI to verify no eventSpec imports crept in. This does not produce output — it is a type-check-only step.

The webpack step (`webpack --config webpack.config.js`) bundles `src/index.ts` into `dist/index.js`. The lite build is NOT bundled by webpack — it ships as CommonJS modules that customer bundlers can tree-shake. This matches the goal: "works universally with any toolchain."

### `yarn build` script — updated
```json
"build": "tsc --emitDeclarationOnly && tsc --project tsconfig.lite.json --noEmit && webpack --config webpack.config.js"
```

The `tsc --project tsconfig.lite.json --noEmit` step verifies the lite source compiles in isolation (only lite-declared includes). If an eventSpec import crept into a lite file, this step fails because eventSpec modules are not in the `tsconfig.lite.json` include list.

---

## Tests

### New test file: `src/__tests__/AvoInspectorLite_test.ts`
Tests the lite entry point to verify:
1. Constructor accepts `apiKey`, `env`, `version`, `appName`, `suffix` (same as full)
2. Constructor does NOT accept `publicEncryptionKey` — verified via TypeScript `@ts-expect-error`. Note: the `@ts-expect-error` annotation requires the following line to actually produce a type error; if the type were not actually removed from the constructor options, `@ts-expect-error` itself would become an error (because no error occurred). This property of the test pattern means a passing test proves the type was actually removed.
3. `trackSchemaFromEvent` returns `Promise<EventProperty[]>` with correct schema (no `encryptedPropertyValue` fields)
4. `trackSchemaFromEvent` returns `[]` for deduplicated events
5. `trackSchema` works without throwing
6. `extractSchema` returns correct schema without encryption
7. Static `batchSize`, `batchFlushSeconds`, `shouldLog`, `networkTimeout` getters work; `batchSize`, `shouldLog`, `networkTimeout` setters work; `batchFlushSeconds` is set only via `setBatchFlushSeconds(n)` instance method (no static setter)
8. Import from `src/lite/index.ts` exports `AvoInspector` and `AvoInspectorEnv`
9. **Static isolation test:** `AvoInspector.batchSize = 99` does NOT change `AvoInspectorLite.batchSize`. This three-line test documents the isolation guarantee and catches any accidental static sharing:
   ```typescript
   it("static batchSize is isolated from full AvoInspector", () => {
     AvoInspector.batchSize = 99;
     expect(AvoInspectorLite.batchSize).not.toBe(99);
     AvoInspector.batchSize = 30; // restore
   });
   ```
   This test is the most likely silent regression vector if a customer runs lite alongside full in the same process during a migration period.

### New test file: `src/__tests__/AvoSchemaParserLite_test.ts`
Tests the lite schema parser:
1. `extractSchema` returns correct property types (string, int, float, boolean, null, list, object)
2. `extractSchema` never sets `encryptedPropertyValue` (even if publicEncryptionKey is passed — it's ignored)
3. Nested object schemas are extracted correctly
4. Array/list schemas are extracted correctly

### Existing tests
All existing tests must pass unchanged. The lite files do not affect the full `AvoInspector.ts` or any existing test imports.

---

## Example Apps

Ship minimal example apps that demonstrate the final bundle size with different toolchains. Each example lives in `examples/lite-size-demos/` and has its own `README.md` showing the exact commands and resulting sizes.

### examples/lite-size-demos/terser-only/

Simulates the GTM customer pipeline: no bundler, just terser on a pre-bundled file.

**Files:**
- `entry.js` — `const { AvoInspector, AvoInspectorEnv } = require("avo-inspector/lite"); ...`
- `build.sh` — `npx esbuild entry.js --bundle --platform=browser --target=es5 --outfile=build/bundle.js && npx terser build/bundle.js --compress passes=2,unsafe=true,unsafe_comps=true --mangle --output build/bundle.min.js`
- `README.md` — Documents the pipeline, shows expected output size, explains what's excluded

**README.md content:**
```markdown
# Lite bundle size — Terser-only pipeline

Simulates a GTM custom HTML tag setup.

## Pipeline
1. esbuild bundles `avo-inspector/lite` into a single file (ES5)
2. terser minifies with aggressive compression

## Run
\`\`\`bash
npm install && bash build.sh
\`\`\`

## Expected output
- `build/bundle.min.js` — ~X KB raw, ~Y KB gzipped
- Zero encryption code, zero event-spec validation code, zero @noble/curves
```

### examples/lite-size-demos/webpack/

Simulates a standard webpack production build.

**Files:**
- `entry.js` — `import { AvoInspector, AvoInspectorEnv } from "avo-inspector/lite"; ...`
- `webpack.config.js` — production mode, single-file UMD output
- `README.md` — Documents pipeline and expected size

### examples/lite-size-demos/rollup/

Simulates a rollup production build.

**Files:**
- `entry.js` — `import { AvoInspector, AvoInspectorEnv } from "avo-inspector/lite"; ...`
- `rollup.config.js` — production build with node-resolve + commonjs plugins
- `README.md` — Documents pipeline and expected size

### Each README shows

1. The exact import used
2. The exact build command
3. The raw, minified, and gzipped output sizes
4. A `grep` proving no encryption/eventSpec code is present

These examples serve as both documentation and CI-verifiable proof that the lite build works universally. The `check:lite-size` script can optionally run all three to verify sizes across toolchains.

**Note:** The exact sizes in the READMEs are filled in after the first build. The examples are included in the npm package via the `"files"` whitelist (`"examples/lite-size-demos/"`) so customers can inspect them, but they are NOT required for the library to function.

---

## Acceptance Criteria

1. `import { AvoInspector, AvoInspectorEnv } from "avo-inspector/lite"` resolves to `dist/lite/index.js` via the package exports map
2. TypeScript: constructing `AvoInspectorLite` with `publicEncryptionKey` causes a compile error (`@ts-expect-error` test pattern)
3. `trackSchemaFromEvent("Test", { prop: "value" })` returns `[{ propertyName: "prop", propertyType: "string" }]` — no `encryptedPropertyValue` field
4. `trackSchemaFromEvent` is async and returns `Promise<EventProperty[]>` — same signature as full
5. `trackSchema` completes without error
6. `extractSchema({ a: 1 })` returns `[{ propertyName: "a", propertyType: "int" }]`
7. `dist/lite/` does NOT contain the string `"AvoEncryption"` — verified by `grep -r "AvoEncryption" dist/lite/`
8. `dist/lite/` does NOT contain the string `"AvoEventSpecFetcher"` — verified by `grep -r "AvoEventSpecFetcher" dist/lite/`
9. `dist/lite/` does NOT contain the string `"safe-regex2"` — verified by `grep -r "safe-regex2" dist/lite/`
10. `dist/lite/` does NOT contain the string `"@noble/curves"` — verified by `grep -r "noble" dist/lite/`
11. `dist/lite/` does NOT contain the string `"EventSpecCache"` — verified by `grep -r "EventSpecCache" dist/lite/`
12. The original `dist/index.js` is unchanged after adding lite files (full build still works)
13. `yarn build` succeeds and produces both `dist/index.js` and `dist/lite/index.js`
14. All existing tests pass (`yarn test`)
15. New tests for `AvoInspectorLite` and `AvoSchemaParserLite` pass
16. `npm pack --dry-run` output includes `dist/lite/index.js` and `dist/lite/index.d.ts`
17. Lite bundle is under 7 KB gzipped — **verified automatically** by `yarn check:lite-size` (not manually)
18. `tsc --project tsconfig.lite.json --noEmit` passes — lite source compiles in isolation
19. `yarn verify:lite-sync` passes — no unexpected drift between lite copies and originals
20. `AvoInspector.batchSize = 99` does NOT change `AvoInspectorLite.batchSize` — verified by static isolation test
21. `examples/lite-size-demos/terser-only/` builds successfully and output is < 7 KB gzipped
22. `examples/lite-size-demos/webpack/` builds successfully and output is < 7 KB gzipped
23. `examples/lite-size-demos/rollup/` builds successfully and output is < 7 KB gzipped
24. Each example has a `README.md` documenting the pipeline, commands, and expected sizes

---

## Verification Commands

After implementing:
```bash
# Build (includes tsconfig.lite.json noEmit check)
yarn build

# Check lite output exists
ls dist/lite/index.js dist/lite/index.d.ts

# Verify no dev code in lite output (all should return no matches)
# Note: grep targets the full dist/lite/ directory, not individual files,
# because each module compiles to a separate CJS file. The grep must be
# recursive (-r) to cover transitive imports.
grep -r "AvoEncryption" dist/lite/
grep -r "AvoEventSpecFetcher" dist/lite/
grep -r "EventSpecCache" dist/lite/
grep -r "noble" dist/lite/
grep -r "safe-regex" dist/lite/

# Run all tests (including static isolation test)
yarn test

# Check pack includes lite
npm pack --dry-run | grep "dist/lite"

# Verify lite copies haven't drifted
yarn verify:lite-sync

# Verify 7 KB size limit (automated, CI-enforced)
yarn check:lite-size
```

---

## Open Questions Resolved

- **AvoStreamId exported from lite?** No. The current `src/index.ts` does not export `AvoStreamId`, so the lite `src/lite/index.ts` matches that — no export.
- **Reuse AvoSchemaParser or create lite copy?** Create a lite copy. The static import of `encryptValue` at the top of `AvoSchemaParser.ts` means even unused-at-runtime encryption code would be included in the lite bundle.
- **Reuse shared modules that import AvoInspector?** No — `AvoNetworkCallsHandler`, `AvoBatcher`, `AvoStreamId`, and `AvoDeduplicator` all transitively depend on `AvoInspector.ts`, which imports eventSpec. Create lite copies of each with a single import line changed.
- **Type-only imports from eventSpec/ in lite copies?** Keep them (`import type { EventSpecMetadata }`) — they are fully erased at compile time and do not cause runtime loading of eventSpec modules.
- **Does `batchFlushSeconds` have a static setter?** No. Verified in `src/AvoInspector.ts`: there is a `static get batchFlushSeconds()` but no `static set batchFlushSeconds`. It is set only via `setBatchFlushSeconds(n)` instance method. `AvoInspectorLite` must replicate this exactly.
- **Is `streamId` a field of `AvoInspectorLite`?** No. `streamId` is listed in the removed private fields. The constructor-body reference `this.streamId = AvoStreamId.streamId` is also removed. `streamId` served only as a gate for the eventSpec initialization block, which is entirely absent in the lite version.
- **Bundlers without exports-map support?** Use direct path `require("avo-inspector/dist/lite/index.js")` as an escape hatch. Document in README.
- **`tsconfig.lite.json` — create or not?** Create unconditionally. The interview brief explicitly requires it, and it provides CI isolation verification value beyond what the main tsconfig offers.
