ad2app-lib 1.42.0 → 1.44.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/index.d.ts +61 -0
- package/dist/analytics/index.js +31 -0
- package/dist/types/I_AccessDenial.d.ts +55 -0
- package/dist/types/I_AccessDenial.js +56 -0
- package/dist/types/I_AnalyticsConsent.d.ts +47 -0
- package/dist/types/I_AnalyticsConsent.js +34 -0
- package/dist/types/agency.d.ts +260 -0
- package/dist/types/agency.js +200 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +3 -0
- package/package.json +1 -1
- package/src/analytics/index.test.ts +54 -0
- package/src/analytics/index.ts +79 -0
- package/src/types/I_AccessDenial.ts +58 -0
- package/src/types/I_AnalyticsConsent.ts +50 -0
- package/src/types/access-denial.test.ts +31 -7
- package/src/types/agency.test.ts +80 -0
- package/src/types/agency.ts +382 -0
- package/src/types/analytics-consent.test.ts +52 -0
- package/src/types/index.ts +4 -0
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* Do NOT rename events after they ship — historical data does not migrate.
|
|
10
10
|
*/
|
|
11
11
|
import type { AccessDenialCode } from "../types/I_AccessDenial";
|
|
12
|
+
import type { SponsoredTier } from "../types/agency";
|
|
12
13
|
/** Canonical PostHog event names. */
|
|
13
14
|
export declare const EVENTS: {
|
|
14
15
|
readonly LANDING_CTA_CLICKED: "landing_cta_clicked";
|
|
@@ -29,6 +30,8 @@ export declare const EVENTS: {
|
|
|
29
30
|
readonly PLAYBOOK_OPENED: "playbook_opened";
|
|
30
31
|
readonly PLAYBOOK_DOWNLOADED: "playbook_downloaded";
|
|
31
32
|
readonly SIGNED_UP: "signed_up";
|
|
33
|
+
readonly ANALYTICS_CONSENT_RECORDED: "analytics_consent_recorded";
|
|
34
|
+
readonly FIRST_AUTHENTICATED: "first_authenticated";
|
|
32
35
|
readonly PROFILE_COMPLETED: "profile_completed";
|
|
33
36
|
readonly LOGGED_IN: "logged_in";
|
|
34
37
|
readonly SOCIAL_ACCOUNT_CONNECTED: "social_account_connected";
|
|
@@ -88,6 +91,21 @@ export declare const EVENTS: {
|
|
|
88
91
|
readonly NATIVE_SESSION_LAUNCHED: "native_session_launched";
|
|
89
92
|
readonly NATIVE_SESSION_RENEWED: "native_session_renewed";
|
|
90
93
|
readonly NATIVE_SESSION_ENDED: "native_session_ended";
|
|
94
|
+
readonly AGENCY_SIGNUP_SUBMITTED: "agency_signup_submitted";
|
|
95
|
+
readonly AGENCY_APPROVED: "agency_approved";
|
|
96
|
+
readonly AGENCY_REJECTED: "agency_rejected";
|
|
97
|
+
readonly AGENCY_MEMBER_INVITED: "agency_member_invited";
|
|
98
|
+
readonly AGENCY_INVITE_LINK_ROTATED: "agency_invite_link_rotated";
|
|
99
|
+
readonly AGENCY_INVITE_OPENED: "agency_invite_opened";
|
|
100
|
+
readonly AGENCY_CONSENT_ACCEPTED: "agency_consent_accepted";
|
|
101
|
+
readonly AGENCY_CONSENT_DECLINED: "agency_consent_declined";
|
|
102
|
+
readonly AGENCY_GRANT_REVOKED: "agency_grant_revoked";
|
|
103
|
+
readonly AGENCY_ROSTER_VIEWED: "agency_roster_viewed";
|
|
104
|
+
readonly AGENCY_CREATOR_VIEWED: "agency_creator_viewed";
|
|
105
|
+
readonly AGENCY_SPONSORSHIP_STARTED: "agency_sponsorship_started";
|
|
106
|
+
readonly AGENCY_SPONSORSHIP_ENDED: "agency_sponsorship_ended";
|
|
107
|
+
readonly AGENCY_DISCOVER_SEARCHED: "agency_discover_searched";
|
|
108
|
+
readonly AGENCY_ACCESS_REQUESTED: "agency_access_requested";
|
|
91
109
|
};
|
|
92
110
|
export type EventName = (typeof EVENTS)[keyof typeof EVENTS];
|
|
93
111
|
/** Which email program a send belongs to (spec 120). Wire values — do not rename. */
|
|
@@ -213,6 +231,28 @@ export type SignInFailureReason = 'canceled' | 'missing_code' | 'invalid_provide
|
|
|
213
231
|
export declare const SIGN_IN_FAILURE_REASONS: readonly ["canceled", "missing_code", "invalid_provider", "no_email", "api_failed", "exception"];
|
|
214
232
|
export type PublishFailureReason = 'auth' | 'rate_limit' | 'media' | 'content' | 'network' | 'platform' | 'unknown';
|
|
215
233
|
/** Property shape per event. Keeps emitters honest across repos. */
|
|
234
|
+
/**
|
|
235
|
+
* The shared shape every agency event carries (spec 175, contract lib-types.md).
|
|
236
|
+
*
|
|
237
|
+
* `organization_id` is mandatory because every agency event is an act BY an
|
|
238
|
+
* organization — an agency event without one cannot be attributed, and the
|
|
239
|
+
* whole point of the set is reading adoption per agency after launch (FR-018).
|
|
240
|
+
*/
|
|
241
|
+
export interface AgencyEventProperties {
|
|
242
|
+
organization_id: string;
|
|
243
|
+
/** Present once a specific consent is the subject of the act. */
|
|
244
|
+
grant_id?: string;
|
|
245
|
+
actor_role: 'owner' | 'member' | 'creator' | 'admin' | 'system';
|
|
246
|
+
origin?: 'invite_link' | 'agency_request';
|
|
247
|
+
terms_version?: string;
|
|
248
|
+
tier?: SponsoredTier;
|
|
249
|
+
/**
|
|
250
|
+
* The creator the act concerns, as a PROPERTY. Never the distinct id an
|
|
251
|
+
* agency-side event is captured under (audit F17): an agency browsing its
|
|
252
|
+
* roster must not write events into its creators' own timelines.
|
|
253
|
+
*/
|
|
254
|
+
creator_user_id?: string;
|
|
255
|
+
}
|
|
216
256
|
export interface EventProperties {
|
|
217
257
|
[EVENTS.LANDING_CTA_CLICKED]: {
|
|
218
258
|
location: 'hero' | 'pricing' | 'final_cta' | 'nav' | 'ai_connector' | 'free_skills';
|
|
@@ -262,6 +302,10 @@ export interface EventProperties {
|
|
|
262
302
|
method: 'email' | 'google';
|
|
263
303
|
role: string;
|
|
264
304
|
};
|
|
305
|
+
[EVENTS.ANALYTICS_CONSENT_RECORDED]: {
|
|
306
|
+
state: 'granted' | 'declined' | 'unset';
|
|
307
|
+
};
|
|
308
|
+
[EVENTS.FIRST_AUTHENTICATED]: Record<string, never>;
|
|
265
309
|
[EVENTS.PROFILE_COMPLETED]: {
|
|
266
310
|
role: string;
|
|
267
311
|
};
|
|
@@ -523,6 +567,23 @@ export interface EventProperties {
|
|
|
523
567
|
[EVENTS.NATIVE_SESSION_ENDED]: {
|
|
524
568
|
cause: 'rejected' | 'sign_out';
|
|
525
569
|
};
|
|
570
|
+
[EVENTS.AGENCY_SIGNUP_SUBMITTED]: AgencyEventProperties;
|
|
571
|
+
[EVENTS.AGENCY_APPROVED]: AgencyEventProperties;
|
|
572
|
+
[EVENTS.AGENCY_REJECTED]: AgencyEventProperties;
|
|
573
|
+
[EVENTS.AGENCY_MEMBER_INVITED]: AgencyEventProperties;
|
|
574
|
+
[EVENTS.AGENCY_INVITE_LINK_ROTATED]: AgencyEventProperties;
|
|
575
|
+
[EVENTS.AGENCY_INVITE_OPENED]: AgencyEventProperties;
|
|
576
|
+
[EVENTS.AGENCY_CONSENT_ACCEPTED]: AgencyEventProperties;
|
|
577
|
+
[EVENTS.AGENCY_CONSENT_DECLINED]: AgencyEventProperties;
|
|
578
|
+
[EVENTS.AGENCY_GRANT_REVOKED]: AgencyEventProperties;
|
|
579
|
+
[EVENTS.AGENCY_ROSTER_VIEWED]: AgencyEventProperties;
|
|
580
|
+
[EVENTS.AGENCY_CREATOR_VIEWED]: AgencyEventProperties;
|
|
581
|
+
[EVENTS.AGENCY_SPONSORSHIP_STARTED]: AgencyEventProperties;
|
|
582
|
+
[EVENTS.AGENCY_SPONSORSHIP_ENDED]: AgencyEventProperties;
|
|
583
|
+
[EVENTS.AGENCY_DISCOVER_SEARCHED]: AgencyEventProperties & {
|
|
584
|
+
matched: boolean;
|
|
585
|
+
};
|
|
586
|
+
[EVENTS.AGENCY_ACCESS_REQUESTED]: AgencyEventProperties;
|
|
526
587
|
}
|
|
527
588
|
/** Canonical person property keys (set via identify / $set). */
|
|
528
589
|
export declare const PERSON_PROPS: {
|
package/dist/analytics/index.js
CHANGED
|
@@ -39,6 +39,15 @@ exports.EVENTS = {
|
|
|
39
39
|
PLAYBOOK_DOWNLOADED: 'playbook_downloaded',
|
|
40
40
|
// Activation (web app)
|
|
41
41
|
SIGNED_UP: 'signed_up', // server-owned (backend, on user creation)
|
|
42
|
+
// 171 (backend#307): the analytics answer itself, emitted by the BACKEND so
|
|
43
|
+
// it arrives for the people who declined — a decline recorded only by the
|
|
44
|
+
// browser that declined is a fact nobody can read. Carries the answer and
|
|
45
|
+
// nothing else.
|
|
46
|
+
ANALYTICS_CONSENT_RECORDED: 'analytics_consent_recorded', // server-owned (backend)
|
|
47
|
+
// 171: the first authenticated request after signup. Server-owned and
|
|
48
|
+
// consent-independent, in the same spirit as T046/T054 — it is what tells
|
|
49
|
+
// "used the product, refused tracking" apart from "never came back".
|
|
50
|
+
FIRST_AUTHENTICATED: 'first_authenticated', // server-owned (backend)
|
|
42
51
|
PROFILE_COMPLETED: 'profile_completed', // the /complete-profile step (influencers)
|
|
43
52
|
LOGGED_IN: 'logged_in',
|
|
44
53
|
SOCIAL_ACCOUNT_CONNECTED: 'social_account_connected',
|
|
@@ -146,6 +155,28 @@ exports.EVENTS = {
|
|
|
146
155
|
NATIVE_SESSION_LAUNCHED: 'native_session_launched',
|
|
147
156
|
NATIVE_SESSION_RENEWED: 'native_session_renewed',
|
|
148
157
|
NATIVE_SESSION_ENDED: 'native_session_ended',
|
|
158
|
+
// Agency domain (175). Every one of these is captured under the acting
|
|
159
|
+
// person's own distinct id: agency-side events under the MEMBER's, with
|
|
160
|
+
// creator_user_id as a property, never under the creator's (audit F17) --
|
|
161
|
+
// otherwise an agency's activity would rewrite the creator's own timeline.
|
|
162
|
+
AGENCY_SIGNUP_SUBMITTED: 'agency_signup_submitted',
|
|
163
|
+
AGENCY_APPROVED: 'agency_approved', // server-owned (backend)
|
|
164
|
+
AGENCY_REJECTED: 'agency_rejected', // server-owned (backend)
|
|
165
|
+
AGENCY_MEMBER_INVITED: 'agency_member_invited',
|
|
166
|
+
AGENCY_INVITE_LINK_ROTATED: 'agency_invite_link_rotated',
|
|
167
|
+
AGENCY_INVITE_OPENED: 'agency_invite_opened',
|
|
168
|
+
AGENCY_CONSENT_ACCEPTED: 'agency_consent_accepted',
|
|
169
|
+
AGENCY_CONSENT_DECLINED: 'agency_consent_declined',
|
|
170
|
+
AGENCY_GRANT_REVOKED: 'agency_grant_revoked',
|
|
171
|
+
AGENCY_ROSTER_VIEWED: 'agency_roster_viewed',
|
|
172
|
+
AGENCY_CREATOR_VIEWED: 'agency_creator_viewed',
|
|
173
|
+
AGENCY_SPONSORSHIP_STARTED: 'agency_sponsorship_started',
|
|
174
|
+
AGENCY_SPONSORSHIP_ENDED: 'agency_sponsorship_ended',
|
|
175
|
+
// Carries `matched` and NEVER the query (FR-015, audit F17): the search term
|
|
176
|
+
// is the one field that would turn product analytics into a record of which
|
|
177
|
+
// creators an agency went looking for.
|
|
178
|
+
AGENCY_DISCOVER_SEARCHED: 'agency_discover_searched',
|
|
179
|
+
AGENCY_ACCESS_REQUESTED: 'agency_access_requested',
|
|
149
180
|
};
|
|
150
181
|
/**
|
|
151
182
|
* Canonical email event property KEYS (spec 120 T001). Emitters and tests
|
|
@@ -24,6 +24,61 @@ export declare const ACCESS_DENIAL_CODES: {
|
|
|
24
24
|
* for this one and do NOT redirect.
|
|
25
25
|
*/
|
|
26
26
|
readonly ENTITLEMENT_PRECONDITION: "entitlement-precondition";
|
|
27
|
+
/**
|
|
28
|
+
* The caller is not an admin.
|
|
29
|
+
*
|
|
30
|
+
* `RoleGuard` answered a 401 with Polish prose until 2026-09-21, which the
|
|
31
|
+
* frontend's session handling reads as an expired session — so a non-admin
|
|
32
|
+
* who opened an admin screen could be signed out rather than refused. A 403
|
|
33
|
+
* with a code says "you may not", which is the true statement.
|
|
34
|
+
*/
|
|
35
|
+
readonly ADMIN_REQUIRED: "admin-required";
|
|
36
|
+
/** The caller holds no accepted membership in any organization. */
|
|
37
|
+
readonly AGENCY_NOT_MEMBER: "agency-not-member";
|
|
38
|
+
/** Their organization is still awaiting a human decision (FR-002). */
|
|
39
|
+
readonly AGENCY_PENDING: "agency-pending";
|
|
40
|
+
/** Their organization was rejected; the reason travels in the body. */
|
|
41
|
+
readonly AGENCY_REJECTED: "agency-rejected";
|
|
42
|
+
/** No live subscription: the roster is dark until the plan is paid (FR-013a). */
|
|
43
|
+
readonly AGENCY_UNPAID: "agency-unpaid";
|
|
44
|
+
/**
|
|
45
|
+
* ONE code for EVERY "cannot see this creator" case — no grant, another
|
|
46
|
+
* organization's creator, a non-existent id, a non-discoverable account.
|
|
47
|
+
*
|
|
48
|
+
* The single code IS the privacy control (FR-011, SC-005, audit F6). Distinct
|
|
49
|
+
* codes would let an agency learn that an account exists by the shape of its
|
|
50
|
+
* refusal, which is the same existence oracle the discover query is
|
|
51
|
+
* structurally built to avoid.
|
|
52
|
+
*/
|
|
53
|
+
readonly AGENCY_NO_ACCESS: "agency-no-access";
|
|
54
|
+
/**
|
|
55
|
+
* The grant exists but does not carry the scope this read needs.
|
|
56
|
+
*
|
|
57
|
+
* Returned ONLY for a creator the caller already holds on the roster — for
|
|
58
|
+
* anyone else it would prove a grant exists, so they get AGENCY_NO_ACCESS
|
|
59
|
+
* instead (SC-012).
|
|
60
|
+
*/
|
|
61
|
+
readonly AGENCY_SCOPE_NOT_GRANTED: "agency-scope-not-granted";
|
|
62
|
+
/** The consent screen is off in this environment (FR-020, the legal gate). */
|
|
63
|
+
readonly AGENCY_CONSENT_DISABLED: "agency-consent-disabled";
|
|
64
|
+
/** The roster invite link expired, was rotated, or its organization is not approved. */
|
|
65
|
+
readonly AGENCY_LINK_INVALID: "agency-link-invalid";
|
|
66
|
+
/** An admin may not decide on an organization they belong to (FR-017). */
|
|
67
|
+
readonly AGENCY_ADMIN_SELF_APPROVAL: "agency-admin-self-approval";
|
|
68
|
+
/**
|
|
69
|
+
* A sole owner tried to leave or delete their account. Refused until they
|
|
70
|
+
* hand ownership over or close the organization explicitly (FR-004): closing
|
|
71
|
+
* carries a fan-out that cancels a subscription and ends sponsorships, and
|
|
72
|
+
* that must be a decision rather than a side effect.
|
|
73
|
+
*/
|
|
74
|
+
readonly AGENCY_SOLE_OWNER_BLOCKED: "agency-sole-owner-blocked";
|
|
75
|
+
/** A member invitation token that is expired, already used, or revoked (FR-005). */
|
|
76
|
+
readonly AGENCY_MEMBER_INVITE_INVALID: "agency-member-invite-invalid";
|
|
77
|
+
/**
|
|
78
|
+
* The invited or accepting person already belongs to an organization (one
|
|
79
|
+
* per person, FR-004). Names the case and NOTHING else about that account.
|
|
80
|
+
*/
|
|
81
|
+
readonly AGENCY_ALREADY_IN_ORGANIZATION: "agency-already-in-organization";
|
|
27
82
|
};
|
|
28
83
|
export type AccessDenialCode = (typeof ACCESS_DENIAL_CODES)[keyof typeof ACCESS_DENIAL_CODES];
|
|
29
84
|
/**
|
|
@@ -27,6 +27,62 @@ exports.ACCESS_DENIAL_CODES = {
|
|
|
27
27
|
* for this one and do NOT redirect.
|
|
28
28
|
*/
|
|
29
29
|
ENTITLEMENT_PRECONDITION: "entitlement-precondition",
|
|
30
|
+
/**
|
|
31
|
+
* The caller is not an admin.
|
|
32
|
+
*
|
|
33
|
+
* `RoleGuard` answered a 401 with Polish prose until 2026-09-21, which the
|
|
34
|
+
* frontend's session handling reads as an expired session — so a non-admin
|
|
35
|
+
* who opened an admin screen could be signed out rather than refused. A 403
|
|
36
|
+
* with a code says "you may not", which is the true statement.
|
|
37
|
+
*/
|
|
38
|
+
ADMIN_REQUIRED: "admin-required",
|
|
39
|
+
// ── Agency domain (spec 175) ──────────────────────────────────────────────
|
|
40
|
+
/** The caller holds no accepted membership in any organization. */
|
|
41
|
+
AGENCY_NOT_MEMBER: "agency-not-member",
|
|
42
|
+
/** Their organization is still awaiting a human decision (FR-002). */
|
|
43
|
+
AGENCY_PENDING: "agency-pending",
|
|
44
|
+
/** Their organization was rejected; the reason travels in the body. */
|
|
45
|
+
AGENCY_REJECTED: "agency-rejected",
|
|
46
|
+
/** No live subscription: the roster is dark until the plan is paid (FR-013a). */
|
|
47
|
+
AGENCY_UNPAID: "agency-unpaid",
|
|
48
|
+
/**
|
|
49
|
+
* ONE code for EVERY "cannot see this creator" case — no grant, another
|
|
50
|
+
* organization's creator, a non-existent id, a non-discoverable account.
|
|
51
|
+
*
|
|
52
|
+
* The single code IS the privacy control (FR-011, SC-005, audit F6). Distinct
|
|
53
|
+
* codes would let an agency learn that an account exists by the shape of its
|
|
54
|
+
* refusal, which is the same existence oracle the discover query is
|
|
55
|
+
* structurally built to avoid.
|
|
56
|
+
*/
|
|
57
|
+
AGENCY_NO_ACCESS: "agency-no-access",
|
|
58
|
+
/**
|
|
59
|
+
* The grant exists but does not carry the scope this read needs.
|
|
60
|
+
*
|
|
61
|
+
* Returned ONLY for a creator the caller already holds on the roster — for
|
|
62
|
+
* anyone else it would prove a grant exists, so they get AGENCY_NO_ACCESS
|
|
63
|
+
* instead (SC-012).
|
|
64
|
+
*/
|
|
65
|
+
AGENCY_SCOPE_NOT_GRANTED: "agency-scope-not-granted",
|
|
66
|
+
/** The consent screen is off in this environment (FR-020, the legal gate). */
|
|
67
|
+
AGENCY_CONSENT_DISABLED: "agency-consent-disabled",
|
|
68
|
+
/** The roster invite link expired, was rotated, or its organization is not approved. */
|
|
69
|
+
AGENCY_LINK_INVALID: "agency-link-invalid",
|
|
70
|
+
/** An admin may not decide on an organization they belong to (FR-017). */
|
|
71
|
+
AGENCY_ADMIN_SELF_APPROVAL: "agency-admin-self-approval",
|
|
72
|
+
/**
|
|
73
|
+
* A sole owner tried to leave or delete their account. Refused until they
|
|
74
|
+
* hand ownership over or close the organization explicitly (FR-004): closing
|
|
75
|
+
* carries a fan-out that cancels a subscription and ends sponsorships, and
|
|
76
|
+
* that must be a decision rather than a side effect.
|
|
77
|
+
*/
|
|
78
|
+
AGENCY_SOLE_OWNER_BLOCKED: "agency-sole-owner-blocked",
|
|
79
|
+
/** A member invitation token that is expired, already used, or revoked (FR-005). */
|
|
80
|
+
AGENCY_MEMBER_INVITE_INVALID: "agency-member-invite-invalid",
|
|
81
|
+
/**
|
|
82
|
+
* The invited or accepting person already belongs to an organization (one
|
|
83
|
+
* per person, FR-004). Names the case and NOTHING else about that account.
|
|
84
|
+
*/
|
|
85
|
+
AGENCY_ALREADY_IN_ORGANIZATION: "agency-already-in-organization",
|
|
30
86
|
};
|
|
31
87
|
/**
|
|
32
88
|
* WHICH precondition the entitlement wall refused on (backend#305, spec 170).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A person's stated answer about analytics, as an ACCOUNT FACT (spec 171,
|
|
3
|
+
* backend#307).
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS. `posthog-js` runs `opt_out_capturing_by_default: true`, and
|
|
6
|
+
* a decline calls `opt_out_capturing()`. That is correct and it is the point:
|
|
7
|
+
* a decline collects nothing. It also means the fact of the decline is known
|
|
8
|
+
* only to that browser — so from the product's side, someone using the app
|
|
9
|
+
* every day with analytics off is indistinguishable from someone who signed up
|
|
10
|
+
* and never came back. Five of nineteen beta testers were in that state on
|
|
11
|
+
* 2026-09-14, and every percentage on the beta dashboard was silently computed
|
|
12
|
+
* over fourteen people while claiming nineteen.
|
|
13
|
+
*
|
|
14
|
+
* RECORDING A DECLINE IS NOT THE TRACKING THAT WAS DECLINED. This carries the
|
|
15
|
+
* answer and nothing else — no page, no behaviour, no session. It is the same
|
|
16
|
+
* instrument as `push_consent_state`, which has recorded "declined" as a
|
|
17
|
+
* separate legal control since spec 162 without anyone calling it surveillance,
|
|
18
|
+
* and it is precisely what lets the product HONOUR a decline while still
|
|
19
|
+
* counting that person as a user rather than as an absence.
|
|
20
|
+
*/
|
|
21
|
+
export declare const ANALYTICS_CONSENT_STATES: {
|
|
22
|
+
/** Never answered the banner. The default, and distinguishable from both
|
|
23
|
+
* answers — an unanswered question is not a "no". */
|
|
24
|
+
readonly UNSET: "unset";
|
|
25
|
+
/** Accepted analytics. Recorded the same way as a decline, so neither answer
|
|
26
|
+
* is ever inferred from the absence of the other. */
|
|
27
|
+
readonly GRANTED: "granted";
|
|
28
|
+
/** Declined analytics. The person is still using the product; they are
|
|
29
|
+
* invisible by choice, which is a different thing from inactive. */
|
|
30
|
+
readonly DECLINED: "declined";
|
|
31
|
+
};
|
|
32
|
+
export type AnalyticsConsentState = (typeof ANALYTICS_CONSENT_STATES)[keyof typeof ANALYTICS_CONSENT_STATES];
|
|
33
|
+
/**
|
|
34
|
+
* The body of `POST /users/analytics-consent`.
|
|
35
|
+
*
|
|
36
|
+
* `unset` IS sendable, and only from one place: the "Cookie settings" link
|
|
37
|
+
* that reopens the choice (review 2026-09-17 M2). The first draft forbade it
|
|
38
|
+
* on the reasoning that the absence of an answer is not an answer anyone
|
|
39
|
+
* gives — true of the banner, false of a RESET, which is a person actively
|
|
40
|
+
* withdrawing their previous answer. Without it the browser went back to
|
|
41
|
+
* `pending` while the column still said `declined`, so the dashboard counted
|
|
42
|
+
* someone as opted out while they were being asked again. A stale record is
|
|
43
|
+
* worse than an absent one.
|
|
44
|
+
*/
|
|
45
|
+
export interface I_AnalyticsConsentBody {
|
|
46
|
+
state: AnalyticsConsentState;
|
|
47
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ANALYTICS_CONSENT_STATES = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* A person's stated answer about analytics, as an ACCOUNT FACT (spec 171,
|
|
6
|
+
* backend#307).
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS EXISTS. `posthog-js` runs `opt_out_capturing_by_default: true`, and
|
|
9
|
+
* a decline calls `opt_out_capturing()`. That is correct and it is the point:
|
|
10
|
+
* a decline collects nothing. It also means the fact of the decline is known
|
|
11
|
+
* only to that browser — so from the product's side, someone using the app
|
|
12
|
+
* every day with analytics off is indistinguishable from someone who signed up
|
|
13
|
+
* and never came back. Five of nineteen beta testers were in that state on
|
|
14
|
+
* 2026-09-14, and every percentage on the beta dashboard was silently computed
|
|
15
|
+
* over fourteen people while claiming nineteen.
|
|
16
|
+
*
|
|
17
|
+
* RECORDING A DECLINE IS NOT THE TRACKING THAT WAS DECLINED. This carries the
|
|
18
|
+
* answer and nothing else — no page, no behaviour, no session. It is the same
|
|
19
|
+
* instrument as `push_consent_state`, which has recorded "declined" as a
|
|
20
|
+
* separate legal control since spec 162 without anyone calling it surveillance,
|
|
21
|
+
* and it is precisely what lets the product HONOUR a decline while still
|
|
22
|
+
* counting that person as a user rather than as an absence.
|
|
23
|
+
*/
|
|
24
|
+
exports.ANALYTICS_CONSENT_STATES = {
|
|
25
|
+
/** Never answered the banner. The default, and distinguishable from both
|
|
26
|
+
* answers — an unanswered question is not a "no". */
|
|
27
|
+
UNSET: "unset",
|
|
28
|
+
/** Accepted analytics. Recorded the same way as a decline, so neither answer
|
|
29
|
+
* is ever inferred from the absence of the other. */
|
|
30
|
+
GRANTED: "granted",
|
|
31
|
+
/** Declined analytics. The person is still using the product; they are
|
|
32
|
+
* invisible by choice, which is a different thing from inactive. */
|
|
33
|
+
DECLINED: "declined",
|
|
34
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agency domain — organizations, memberships and creator access grants (spec 175).
|
|
3
|
+
*
|
|
4
|
+
* The shape behind "an agency sees its creators' numbers, because the creators
|
|
5
|
+
* said yes". Two doors, one product: a creator account is unchanged by any of
|
|
6
|
+
* this, and gains at most one grant.
|
|
7
|
+
*
|
|
8
|
+
* The one idea worth carrying into every consumer: a GRANT IS READ-ONLY AND
|
|
9
|
+
* CREATOR-OWNED. The agency asks for scopes; only a creator's own act ever
|
|
10
|
+
* writes `grantedScopes` (FR-008, SC-002). Nothing here can express a write on
|
|
11
|
+
* a creator's account, and that is deliberate — the absence is the feature.
|
|
12
|
+
*
|
|
13
|
+
* See specs/175-agency-roster/contracts/lib-types.md.
|
|
14
|
+
*/
|
|
15
|
+
import type { PublishPlatform } from '../publish-limits/types';
|
|
16
|
+
/**
|
|
17
|
+
* Paths that BOTH repos have to agree on, with one home here.
|
|
18
|
+
*
|
|
19
|
+
* The backend mails this path; the frontend registers it. They deploy
|
|
20
|
+
* separately, so nothing else notices them drifting — and the first version of
|
|
21
|
+
* that link pointed at a route no repo served, which is how an entire user
|
|
22
|
+
* story shipped with its headline capability unreachable.
|
|
23
|
+
*
|
|
24
|
+
* A test in each repo asserts its own side against these constants. The first
|
|
25
|
+
* attempt at that guard read across the filesystem into a sibling checkout,
|
|
26
|
+
* which worked on one laptop and threw ENOENT in CI; a shared constant is the
|
|
27
|
+
* version that actually runs.
|
|
28
|
+
*/
|
|
29
|
+
export declare const AGENCY_ROUTE_PATHS: {
|
|
30
|
+
/** A colleague accepting a team invitation (FR-005). */
|
|
31
|
+
readonly joinTeam: "/agency/join-team";
|
|
32
|
+
/** A creator consenting to an agency's access (FR-007). */
|
|
33
|
+
readonly join: "/agency/join";
|
|
34
|
+
/** A creator answering an access request (FR-016). */
|
|
35
|
+
readonly requests: "/agency/requests";
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The complete set of reads a grant may carry. A CEILING, not a starting point.
|
|
39
|
+
*
|
|
40
|
+
* All three are reads of what the creator already sees on their own screens.
|
|
41
|
+
* Private messages, comment replies and moderation, publishing, connection
|
|
42
|
+
* changes and settings are absent by construction (FR-010) — there is no scope
|
|
43
|
+
* string that could name them, so a widened grant is a spec change rather than
|
|
44
|
+
* a config change.
|
|
45
|
+
*/
|
|
46
|
+
export declare const AGENCY_GRANT_SCOPES: readonly ["analytics", "comments", "posts"];
|
|
47
|
+
export type I_AgencyGrantScope = (typeof AGENCY_GRANT_SCOPES)[number];
|
|
48
|
+
/**
|
|
49
|
+
* Narrowing guard — an unrecognised scope must fail closed.
|
|
50
|
+
*
|
|
51
|
+
* Scope strings arrive from request bodies (the agency's ask, the creator's
|
|
52
|
+
* answer), so this is a trust boundary, not a convenience.
|
|
53
|
+
*/
|
|
54
|
+
export declare const isAgencyGrantScope: (value: string) => value is I_AgencyGrantScope;
|
|
55
|
+
/** `'brand'` arrives with spec 148; one column, no enum table. */
|
|
56
|
+
export type AgencyOrganizationType = 'agency';
|
|
57
|
+
/**
|
|
58
|
+
* `closed` was added by the 2026-09-21 requirements review (FR-004b).
|
|
59
|
+
*
|
|
60
|
+
* It exists so closure has an end state that is NOT a deleted row: grants are
|
|
61
|
+
* `on delete cascade` from the organization, so hard-deleting one would destroy
|
|
62
|
+
* the consent records FR-009 says are never deleted — and which are also the
|
|
63
|
+
* invoice basis.
|
|
64
|
+
*/
|
|
65
|
+
export declare const AGENCY_ORGANIZATION_STATUSES: readonly ["pending", "approved", "rejected", "closed"];
|
|
66
|
+
export type AgencyOrganizationStatus = (typeof AGENCY_ORGANIZATION_STATUSES)[number];
|
|
67
|
+
/** Mirrored from Stripe. `comped` is a 100% coupon and is treated as active. */
|
|
68
|
+
export type AgencySubscriptionStatus = 'none' | 'active' | 'trialing' | 'past_due' | 'unpaid' | 'canceled' | 'comped';
|
|
69
|
+
/** Two roles, no more (FR-004). Only the owner reaches billing, members and the link. */
|
|
70
|
+
export type AgencyMemberRole = 'owner' | 'member';
|
|
71
|
+
export type SponsoredTier = 'starter' | 'pro';
|
|
72
|
+
export interface I_AgencyOrganization {
|
|
73
|
+
id: string;
|
|
74
|
+
type: AgencyOrganizationType;
|
|
75
|
+
name: string;
|
|
76
|
+
website?: string;
|
|
77
|
+
/** NIP/KRS or a foreign equivalent — free text, verified by a human, never by code. */
|
|
78
|
+
registryId: string;
|
|
79
|
+
/** ISO-3166 alpha-2 of the register the identifier belongs to. */
|
|
80
|
+
registryCountry: string;
|
|
81
|
+
contactEmail: string;
|
|
82
|
+
status: AgencyOrganizationStatus;
|
|
83
|
+
/** The reason an admin gave; shown to a rejected applicant (FR-002). */
|
|
84
|
+
statusReason?: string;
|
|
85
|
+
subscriptionStatus: AgencySubscriptionStatus;
|
|
86
|
+
/** The date sponsorships run to when a grant is revoked or the plan lapses. */
|
|
87
|
+
currentPeriodEnd?: string;
|
|
88
|
+
createdAt: string;
|
|
89
|
+
}
|
|
90
|
+
export interface I_AgencyMember {
|
|
91
|
+
id: string;
|
|
92
|
+
organizationId: string;
|
|
93
|
+
/** Null while an e-mail invitation is outstanding — nobody has accepted yet. */
|
|
94
|
+
userId?: string;
|
|
95
|
+
invitedEmail: string;
|
|
96
|
+
role: AgencyMemberRole;
|
|
97
|
+
invitedAt: string;
|
|
98
|
+
/** "Who is in" is exactly `acceptedAt != null`. */
|
|
99
|
+
acceptedAt?: string;
|
|
100
|
+
revokedAt?: string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The five states the data model pins.
|
|
104
|
+
*
|
|
105
|
+
* `declined` and `expired` belong to the US5 request path (FR-016) and were
|
|
106
|
+
* missing from the spec's own Key Entities until the 2026-09-21 review. There
|
|
107
|
+
* is no transition OUT of `revoked`, `declined` or `expired`: a new consent is
|
|
108
|
+
* a new row, so the record of what was agreed is never overwritten.
|
|
109
|
+
*/
|
|
110
|
+
export declare const CREATOR_ACCESS_GRANT_STATES: readonly ["requested", "active", "declined", "expired", "revoked"];
|
|
111
|
+
export type CreatorAccessGrantState = (typeof CREATOR_ACCESS_GRANT_STATES)[number];
|
|
112
|
+
export type CreatorAccessGrantOrigin = 'invite_link' | 'agency_request';
|
|
113
|
+
/** Who ended it. Shown in the agency's past-creators history (FR-013b). */
|
|
114
|
+
export type CreatorAccessGrantRevokedBy = 'creator' | 'agency' | 'organization' | 'account-deleted' | 'purge' | 'system';
|
|
115
|
+
export interface I_CreatorAccessGrant {
|
|
116
|
+
id: string;
|
|
117
|
+
/**
|
|
118
|
+
* Null once the creator's user row is hard-purged (audit D-D): the grant
|
|
119
|
+
* survives as the consent record, with `creatorIdentity` standing in for the
|
|
120
|
+
* person who is no longer there.
|
|
121
|
+
*/
|
|
122
|
+
creatorUserId?: string;
|
|
123
|
+
organizationId: string;
|
|
124
|
+
organizationName: string;
|
|
125
|
+
/** What the agency asked for. */
|
|
126
|
+
requestedScopes: I_AgencyGrantScope[];
|
|
127
|
+
/** What the creator actually granted — a subset, possibly smaller, never larger. */
|
|
128
|
+
grantedScopes: I_AgencyGrantScope[];
|
|
129
|
+
/** Frozen at grant time; what the history shows after a purge. */
|
|
130
|
+
creatorIdentity: {
|
|
131
|
+
handle: string;
|
|
132
|
+
displayName?: string;
|
|
133
|
+
};
|
|
134
|
+
state: CreatorAccessGrantState;
|
|
135
|
+
origin: CreatorAccessGrantOrigin;
|
|
136
|
+
requestedAt?: string;
|
|
137
|
+
expiresAt?: string;
|
|
138
|
+
grantedAt?: string;
|
|
139
|
+
/** Which terms the creator accepted. A `draft-` prefix means pre-addendum (FR-020). */
|
|
140
|
+
termsVersion?: string;
|
|
141
|
+
revokedAt?: string;
|
|
142
|
+
revokedBy?: CreatorAccessGrantRevokedBy;
|
|
143
|
+
/**
|
|
144
|
+
* The agency covering this creator's plan. `until` is set when the
|
|
145
|
+
* sponsorship is ending — it runs to the close of the period already paid
|
|
146
|
+
* for, so a revoke never silently drops the creator's plan (FR-014).
|
|
147
|
+
*/
|
|
148
|
+
sponsorship?: {
|
|
149
|
+
tier: SponsoredTier;
|
|
150
|
+
since: string;
|
|
151
|
+
until?: string;
|
|
152
|
+
};
|
|
153
|
+
/** Which platforms the grant reaches right now — derived at read time, never stored. */
|
|
154
|
+
platformsReached: PublishPlatform[];
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Roster row states, DECLARED IN PRECEDENCE ORDER (data-model §Derived).
|
|
158
|
+
*
|
|
159
|
+
* A row holds exactly one state: the first of these that applies, most-blocking
|
|
160
|
+
* first. `revoked` is deliberately absent — a revoked creator leaves the roster
|
|
161
|
+
* entirely for the settings history (FR-011).
|
|
162
|
+
*/
|
|
163
|
+
export declare const AGENCY_ROSTER_ROW_STATES: readonly ["requested", "lapsed", "disconnected", "stale", "active"];
|
|
164
|
+
export type AgencyRosterRowState = (typeof AGENCY_ROSTER_ROW_STATES)[number];
|
|
165
|
+
/**
|
|
166
|
+
* What "needs attention" means — ONE home for the KPI tile, the roster segment
|
|
167
|
+
* and the rail badge, so the three can never drift apart (FR-011).
|
|
168
|
+
*
|
|
169
|
+
* `requested` is excluded on purpose: it waits on the CREATOR, and FR-004a says
|
|
170
|
+
* the badge means something waits on the agency.
|
|
171
|
+
*/
|
|
172
|
+
export declare const AGENCY_ROSTER_NEEDS_ATTENTION_STATES: readonly ["lapsed", "disconnected", "stale"];
|
|
173
|
+
export type AgencyRosterNeedsAttentionState = (typeof AGENCY_ROSTER_NEEDS_ATTENTION_STATES)[number];
|
|
174
|
+
export interface I_AgencyRosterRow {
|
|
175
|
+
grantId: string;
|
|
176
|
+
creator: {
|
|
177
|
+
id: string;
|
|
178
|
+
handle: string;
|
|
179
|
+
};
|
|
180
|
+
platforms: PublishPlatform[];
|
|
181
|
+
state: AgencyRosterRowState;
|
|
182
|
+
grantedScopes: I_AgencyGrantScope[];
|
|
183
|
+
lastSyncAt?: string;
|
|
184
|
+
}
|
|
185
|
+
export interface I_AgencyRoster {
|
|
186
|
+
totals: {
|
|
187
|
+
creators: number;
|
|
188
|
+
active: number;
|
|
189
|
+
/** Summed only over creators holding `analytics`; disconnected platforms excluded. */
|
|
190
|
+
followers: number;
|
|
191
|
+
followersDelta7d: number;
|
|
192
|
+
/** Calendar week, Mon-Sun, organization timezone, creators holding `comments`. */
|
|
193
|
+
commentsThisWeek: number;
|
|
194
|
+
/** Count of rows in AGENCY_ROSTER_NEEDS_ATTENTION_STATES. */
|
|
195
|
+
needsAttention: number;
|
|
196
|
+
};
|
|
197
|
+
items: I_AgencyRosterRow[];
|
|
198
|
+
}
|
|
199
|
+
/** What a sponsored creator sees on their OWN subscription screen (FR-014). */
|
|
200
|
+
export interface I_SponsoredBy {
|
|
201
|
+
organizationName: string;
|
|
202
|
+
tier: SponsoredTier;
|
|
203
|
+
/** Present once the sponsorship is ending: the date their plan runs to. */
|
|
204
|
+
until?: string;
|
|
205
|
+
}
|
|
206
|
+
export declare class CreateAgencyOrganizationDto {
|
|
207
|
+
name: string;
|
|
208
|
+
website?: string;
|
|
209
|
+
registryId: string;
|
|
210
|
+
registryCountry: string;
|
|
211
|
+
contactEmail: string;
|
|
212
|
+
constructor(data: CreateAgencyOrganizationDto);
|
|
213
|
+
}
|
|
214
|
+
export declare class InviteAgencyMemberDto {
|
|
215
|
+
email: string;
|
|
216
|
+
constructor(data: InviteAgencyMemberDto);
|
|
217
|
+
}
|
|
218
|
+
export declare class RedeemAgencyInviteDto {
|
|
219
|
+
token: string;
|
|
220
|
+
constructor(data: RedeemAgencyInviteDto);
|
|
221
|
+
}
|
|
222
|
+
export declare class AcceptAgencyMemberInviteDto {
|
|
223
|
+
token: string;
|
|
224
|
+
constructor(data: AcceptAgencyMemberInviteDto);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Accepting consent. Either arm identifies the grant being answered; `scopes`
|
|
228
|
+
* is the creator's answer and defaults to the requested set when absent.
|
|
229
|
+
*/
|
|
230
|
+
export declare class AcceptGrantDto {
|
|
231
|
+
token?: string;
|
|
232
|
+
grantId?: string;
|
|
233
|
+
scopes?: I_AgencyGrantScope[];
|
|
234
|
+
constructor(data: AcceptGrantDto);
|
|
235
|
+
}
|
|
236
|
+
/** The creator narrowing or widening a live grant — the only other write path. */
|
|
237
|
+
export declare class UpdateGrantScopesDto {
|
|
238
|
+
scopes: I_AgencyGrantScope[];
|
|
239
|
+
constructor(data: UpdateGrantScopesDto);
|
|
240
|
+
}
|
|
241
|
+
export declare class SponsorCreatorDto {
|
|
242
|
+
tier: SponsoredTier;
|
|
243
|
+
constructor(data: SponsorCreatorDto);
|
|
244
|
+
}
|
|
245
|
+
export declare class AgencyDiscoverQueryDto {
|
|
246
|
+
/** An EXACT platform username or e-mail. Never recorded in analytics (FR-015). */
|
|
247
|
+
q: string;
|
|
248
|
+
constructor(data: AgencyDiscoverQueryDto);
|
|
249
|
+
}
|
|
250
|
+
export declare class AgencyAccessRequestDto {
|
|
251
|
+
userId: string;
|
|
252
|
+
scopes: I_AgencyGrantScope[];
|
|
253
|
+
constructor(data: AgencyAccessRequestDto);
|
|
254
|
+
}
|
|
255
|
+
export declare class AdminSetOrganizationStatusDto {
|
|
256
|
+
status: Extract<AgencyOrganizationStatus, 'approved' | 'rejected'>;
|
|
257
|
+
/** Required on reject — a rejected applicant is shown this (FR-002). */
|
|
258
|
+
reason?: string;
|
|
259
|
+
constructor(data: AdminSetOrganizationStatusDto);
|
|
260
|
+
}
|