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.
@@ -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: {
@@ -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
+ }