@usefidel/contracts 0.11.0 → 0.12.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/dist/analytics.d.ts +63 -15
- package/dist/analytics.js +22 -7
- package/package.json +1 -1
package/dist/analytics.d.ts
CHANGED
|
@@ -6,14 +6,29 @@
|
|
|
6
6
|
* (webapp, landing).
|
|
7
7
|
*
|
|
8
8
|
* WHY THIS IS A PACKAGE AND NOT A LOCAL FILE PER SURFACE.
|
|
9
|
-
* The webapp and the extension each
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
9
|
+
* The webapp and the extension each wrote their own identity rule, and the two
|
|
10
|
+
* rules were written to agree. In the common case they did: both normally
|
|
11
|
+
* identified a person by email.
|
|
12
|
+
*
|
|
13
|
+
* The defect was in what happened around that agreement.
|
|
14
|
+
*
|
|
15
|
+
* 1. EMAIL IS MUTABLE. A `distinct_id` must be stable for the life of the
|
|
16
|
+
* person, and an email address is not — changing it forks one human into
|
|
17
|
+
* two identities with no event connecting them.
|
|
18
|
+
* 2. THE FALLBACKS DIVERGED. Email is not always available, and each surface
|
|
19
|
+
* fell back into a DIFFERENT namespace when it was missing — a Supabase
|
|
20
|
+
* UUID on one side, a Figma handle on the other. So the same human was
|
|
21
|
+
* identified consistently right up until the moment they were not, which
|
|
22
|
+
* is the hardest version of this bug to see.
|
|
23
|
+
* 3. NOTHING GATED THE DUPLICATE. The rule was written out twice, in two
|
|
24
|
+
* repositories, with no check that the two copies still said the same
|
|
25
|
+
* thing. A drift would have been silent — and an earlier comment in
|
|
26
|
+
* `extension/src/lib/posthog.ts` asserting the two agreed is exactly the
|
|
27
|
+
* kind of claim a gate exists to replace.
|
|
28
|
+
*
|
|
29
|
+
* Nothing failed loudly. Retention cohorts were simply wrong, and stayed wrong.
|
|
30
|
+
* The package is the only mechanism that spans both repositories, so it is the
|
|
31
|
+
* only place the rule can be stated once.
|
|
17
32
|
*
|
|
18
33
|
* WHAT THIS OWNS: event NAMES and property TYPES.
|
|
19
34
|
* WHAT IT DOES NOT OWN: SDK initialization, identity resolution, storage,
|
|
@@ -42,7 +57,21 @@ import { type RunErrorCode } from './run-errors.js';
|
|
|
42
57
|
* history survives; the meaning of their events does not.
|
|
43
58
|
*/
|
|
44
59
|
export declare const ANALYTICS_SCHEMA_VERSION: 2;
|
|
45
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Which PRODUCT JOURNEY the event belongs to — not which process emitted it.
|
|
62
|
+
*
|
|
63
|
+
* This comment used to read "server-side surfaces do not emit at all", and that
|
|
64
|
+
* stopped being true when authoritative terminal events moved into the
|
|
65
|
+
* `web-validate` finalizer. A validation started from the web app is a webapp
|
|
66
|
+
* validation whether the browser or the backend reports its outcome, so the
|
|
67
|
+
* backend's `web_validation_completed` correctly carries `surface: 'webapp'`.
|
|
68
|
+
*
|
|
69
|
+
* Making the backend its own surface would split one funnel in half: the
|
|
70
|
+
* submission would sit under `webapp` and its completion under something else,
|
|
71
|
+
* and no tile could relate them without a union. Which process emitted an event
|
|
72
|
+
* is answered by the event NAME and by `release`, both of which already
|
|
73
|
+
* distinguish the two.
|
|
74
|
+
*/
|
|
46
75
|
export type AnalyticsSurface = 'landing' | 'webapp' | 'extension';
|
|
47
76
|
/** Which deployment emitted the event. Drives the default dashboard filter. */
|
|
48
77
|
export type AnalyticsEnvironment = 'production' | 'staging' | 'development';
|
|
@@ -130,13 +159,16 @@ export type GaEventName = (typeof GA_EVENT_NAMES)[number];
|
|
|
130
159
|
export type SecondaryEventName = (typeof SECONDARY_EVENT_NAMES)[number];
|
|
131
160
|
export type AnalyticsEventName = GaEventName | SecondaryEventName;
|
|
132
161
|
/**
|
|
133
|
-
* Stamped on every schema-2 event by the
|
|
162
|
+
* Stamped on every schema-2 event by the emitter, not by call sites — the
|
|
163
|
+
* browser wrapper for client events, `_shared/analytics.ts` for the backend
|
|
164
|
+
* terminal events.
|
|
134
165
|
*
|
|
135
|
-
* `
|
|
136
|
-
* them without a network request it is not allowed to make:
|
|
137
|
-
* `profiles`, not in the Supabase JWT. Team-level analysis uses
|
|
166
|
+
* `fidel_team_id` and `is_internal` are optional because the extension cannot
|
|
167
|
+
* get them without a network request it is not allowed to make: the team lives
|
|
168
|
+
* in `profiles`, not in the Supabase JWT. Team-level analysis uses
|
|
138
169
|
* `validation_runs.team_id` in the database instead. The webapp has both and
|
|
139
|
-
* always sends them
|
|
170
|
+
* always sends them, and the backend reads both from the resolved team
|
|
171
|
+
* context it already loaded to run the subscription gate.
|
|
140
172
|
*
|
|
141
173
|
* There is no `user_id` field. The Supabase UUID is the `distinct_id`;
|
|
142
174
|
* duplicating it as a property invites two sources of truth for identity.
|
|
@@ -147,7 +179,23 @@ export interface AnalyticsContext {
|
|
|
147
179
|
surface: AnalyticsSurface;
|
|
148
180
|
/** Git SHA (webapp/landing) or `manifest.json` version (extension). */
|
|
149
181
|
release: string;
|
|
150
|
-
|
|
182
|
+
/**
|
|
183
|
+
* The Fidel team, PREFIXED — and the prefix is not cosmetic.
|
|
184
|
+
*
|
|
185
|
+
* `team_id` is a reserved top-level column on PostHog's own events table (it
|
|
186
|
+
* holds the PostHog project id). A property of that name is still stored in
|
|
187
|
+
* the payload, but `properties.team_id` resolves to the reserved column and
|
|
188
|
+
* returns NULL, so every dashboard filter and breakdown built on it silently
|
|
189
|
+
* matches nothing. Measured on staging 2026-09-08:
|
|
190
|
+
*
|
|
191
|
+
* JSONExtractString(properties,'team_id') -> b34bb03c-… (the real value)
|
|
192
|
+
* properties.team_id -> NULL
|
|
193
|
+
* team_id -> 330851 (PostHog's project)
|
|
194
|
+
*
|
|
195
|
+
* That is the worst failure shape available: instrumented, ingested, and
|
|
196
|
+
* invisible. `is_internal` does not collide and keeps its plain name.
|
|
197
|
+
*/
|
|
198
|
+
fidel_team_id?: string;
|
|
151
199
|
is_internal?: boolean;
|
|
152
200
|
}
|
|
153
201
|
/**
|
package/dist/analytics.js
CHANGED
|
@@ -6,14 +6,29 @@
|
|
|
6
6
|
* (webapp, landing).
|
|
7
7
|
*
|
|
8
8
|
* WHY THIS IS A PACKAGE AND NOT A LOCAL FILE PER SURFACE.
|
|
9
|
-
* The webapp and the extension each
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* comment asserting the two agreed. They did not. Nothing failed; retention
|
|
13
|
-
* cohorts were simply wrong, and stayed wrong, because the two "authoritative"
|
|
14
|
-
* definitions lived in two repositories with no gate between them.
|
|
9
|
+
* The webapp and the extension each wrote their own identity rule, and the two
|
|
10
|
+
* rules were written to agree. In the common case they did: both normally
|
|
11
|
+
* identified a person by email.
|
|
15
12
|
*
|
|
16
|
-
* The
|
|
13
|
+
* The defect was in what happened around that agreement.
|
|
14
|
+
*
|
|
15
|
+
* 1. EMAIL IS MUTABLE. A `distinct_id` must be stable for the life of the
|
|
16
|
+
* person, and an email address is not — changing it forks one human into
|
|
17
|
+
* two identities with no event connecting them.
|
|
18
|
+
* 2. THE FALLBACKS DIVERGED. Email is not always available, and each surface
|
|
19
|
+
* fell back into a DIFFERENT namespace when it was missing — a Supabase
|
|
20
|
+
* UUID on one side, a Figma handle on the other. So the same human was
|
|
21
|
+
* identified consistently right up until the moment they were not, which
|
|
22
|
+
* is the hardest version of this bug to see.
|
|
23
|
+
* 3. NOTHING GATED THE DUPLICATE. The rule was written out twice, in two
|
|
24
|
+
* repositories, with no check that the two copies still said the same
|
|
25
|
+
* thing. A drift would have been silent — and an earlier comment in
|
|
26
|
+
* `extension/src/lib/posthog.ts` asserting the two agreed is exactly the
|
|
27
|
+
* kind of claim a gate exists to replace.
|
|
28
|
+
*
|
|
29
|
+
* Nothing failed loudly. Retention cohorts were simply wrong, and stayed wrong.
|
|
30
|
+
* The package is the only mechanism that spans both repositories, so it is the
|
|
31
|
+
* only place the rule can be stated once.
|
|
17
32
|
*
|
|
18
33
|
* WHAT THIS OWNS: event NAMES and property TYPES.
|
|
19
34
|
* WHAT IT DOES NOT OWN: SDK initialization, identity resolution, storage,
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"The source of truth still lives in the monorepo at packages/contracts/."
|
|
8
8
|
],
|
|
9
9
|
"name": "@usefidel/contracts",
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.12.0",
|
|
11
11
|
"description": "Shared, code-free contracts between Fidel surfaces. Run-error taxonomy, theme-intake wire types, and the canonical fidel.config.json builders.",
|
|
12
12
|
"license": "UNLICENSED",
|
|
13
13
|
"private": false,
|