@sonordev/contracts 2.13.0 → 2.15.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/CHANGELOG.md +15 -0
- package/README.md +48 -1
- package/dist/campaign-normalize.d.ts +2 -0
- package/dist/funnels.d.ts +576 -0
- package/dist/funnels.js +617 -0
- package/dist/funnels.mjs +560 -0
- package/dist/inbox.d.ts +42 -0
- package/dist/inbox.js +14 -0
- package/dist/inbox.mjs +9 -0
- package/dist/llms.d.ts +200 -0
- package/dist/llms.js +1002 -8
- package/dist/llms.mjs +989 -9
- package/dist/publication-branding.d.ts +6 -1
- package/dist/publication-branding.js +1 -1
- package/dist/publication-branding.mjs +1 -1
- package/dist/site-cache.d.ts +2 -0
- package/dist/site-cache.js +3 -1
- package/dist/site-cache.mjs +3 -1
- package/package.json +13 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- `llms` holds the rules for what llms.txt says about each page, so the Sonor API and site-kit's local generator build page lines the same way. New: `pickLlmsPageDescription` and `resolveLlmsPageDescription` (summary, curated meta, served meta, schema description, then a cleaned scrape), `llmsPageListNote`, `llmsPageTitle`, `isLlmsListablePage`, `selectLlmsPages`, `groupLlmsPages`, `llmsBlockquoteSummary`, `llmsContactLines`, `llmsAbsoluteUrl`, `cleanScrapedPageText`, `stripContactDetails`, `stripPageFiller` and `LLMS_DESCRIPTION_MAX_LENGTH`.
|
|
6
|
+
- `sanitizeLlmsPublicSummary` removes a phone number or email address, with the "call" or "email us at" in front of it, and a "This page covers" lead-in, and keeps the rest. It used to return `null` for any summary that mentioned a phone or email, and a page with no summary is described by its scrape.
|
|
7
|
+
|
|
8
|
+
## 2.15.0
|
|
9
|
+
|
|
10
|
+
- Add `@sonordev/contracts/funnels` with bounded incomplete drafts, launch-ready definitions, command envelopes, brand presentation overrides, readiness and public page validation. Runtime parsers and JSON schemas share the same shapes.
|
|
11
|
+
- Add the canonical funnel content cache tag and campaign normalization used by managed landing pages. Existing publication logo roles and inbox behavior remain available.
|
|
12
|
+
|
|
13
|
+
## 2.14.0
|
|
14
|
+
|
|
15
|
+
- Publication branding adds optional `identity.squareLogoUrl` and `identity.fullLogoUrl` for a square publication mark and its full/display logo. Both use the existing URL validation and support `null` in patches to clear an override. Theme variants in `identity.logos`, the text `identity.wordmark`, and publisher attribution remain separate. Existing saved profiles keep their shape and version.
|
|
16
|
+
- Add `@sonordev/contracts/inbox`: the one rule for which inbox cards are decisions that "Mark all read" and "Dismiss all" leave alone. `isDecisionCard(card)` counts an approval, the ledger's unconfirmed-action card and any card carrying a proposal; `DECISION_INSIGHT_TYPES`, `APPROVAL_NEEDED_INSIGHT_TYPE` and `LEDGER_UNCONFIRMED_INSIGHT_TYPE` are the types behind it. The Signal API and the dashboard read the same rule, so a bulk action's preview matches what it writes.
|
|
17
|
+
|
|
3
18
|
## 2.13.0
|
|
4
19
|
|
|
5
20
|
- Publication branding adds `editorial.focusClosingBackground`, with `planned_photo` and `article_atmosphere` choices. The setting inherits through publication and platform layers, and supports clearing an override. Article atmosphere closing frames reuse saved artwork and the real author portrait; other publications retain their existing behavior.
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ pnpm add @sonordev/contracts
|
|
|
18
18
|
| `@sonordev/contracts/sites` | The canonical shape of a site host (the multi-site `site` dimension) | every ingest and read that filters by site |
|
|
19
19
|
| `@sonordev/contracts/seo-pages` | Which `seo_pages` row a path resolves to on a multi-site project | the Sonor API, Signal, site-kit's sitemap sync |
|
|
20
20
|
| `@sonordev/contracts/seo-meta` | Whether a title or description is fit to ship | site-kit, Signal's SEO gate |
|
|
21
|
-
| `@sonordev/contracts/llms` | llms.txt sanitizers, the word-safe cut they use (`clipAtWordBoundary`), and the contract version | site-kit, the Sonor API, Signal's schema validation |
|
|
21
|
+
| `@sonordev/contracts/llms` | llms.txt sanitizers, the word-safe cut they use (`clipAtWordBoundary`), what each page line says (`pickLlmsPageDescription`: summary, curated meta, served meta, schema description, then a cleaned scrape; `llmsPageTitle`, `isLlmsListablePage`, `groupLlmsPages`, `selectLlmsPages`) and the contract version | site-kit, the Sonor API, Signal's schema validation |
|
|
22
22
|
| `@sonordev/contracts/forms` | The honeypot policy | site-kit forms, the Sonor API's form intake |
|
|
23
23
|
| `@sonordev/contracts/fleet` | The fleet heartbeat's wire shape | site-kit, the Sonor API |
|
|
24
24
|
| `@sonordev/contracts/slots` | Slot payload signing, contract v2 (HMAC; Node only). Signs and verifies v1 for older site-kit releases | the Sonor API, site-kit on the server |
|
|
@@ -38,6 +38,8 @@ pnpm add @sonordev/contracts
|
|
|
38
38
|
| `@sonordev/contracts/broadcast-layouts` | Which Broadcast card layouts are drawn (centered `headline`, low `anchor`), which retired IDs still read and what each draws as (`normalizeArticleCoverLayout`), the Broadcast styles, and the review compositions with the frozen draw that gives each review its own (`reviewCardVariationFromDigest`) | the Sonor API's card renderers and settings, Signal's agent tools, the dashboard's pickers |
|
|
39
39
|
| `@sonordev/contracts/publication-branding` | What a publication's branding may hold and what a change may set (`createPublicationBrandingValidator`), how a change merges, the editorial charter, and safe font stacks | the Sonor API stores and resolves it, Signal checks an agent's change before filing it, the dashboard edits it |
|
|
40
40
|
| `@sonordev/contracts/public-url-host` | Which literal hosts are refused as public fetch destinations (`isBlockedPublicUrlHost`) | the Sonor API and Signal's URL validators and network fetchers |
|
|
41
|
+
| `@sonordev/contracts/funnels` | Incomplete drafts, launch definitions, command/readiness/receipt schemas, bounded public pages and presentation-only brand overrides | Sites, APIs, UI and agent adapters share the same rules |
|
|
42
|
+
| `@sonordev/contracts/inbox` | Which inbox cards are decisions a bulk action leaves alone (`isDecisionCard`, `DECISION_INSIGHT_TYPES`) | Signal's inbox decides what "Mark all read" and "Dismiss all" may touch, the dashboard previews the same result before the write |
|
|
41
43
|
|
|
42
44
|
## Focus closing artwork
|
|
43
45
|
|
|
@@ -63,6 +65,31 @@ isn't proof a destination is public: the fetcher still validates the protocol,
|
|
|
63
65
|
credentials and port, checks the DNS addresses it connects to, and applies its
|
|
64
66
|
redirect policy on each hop. Those network protections stay in the APIs.
|
|
65
67
|
|
|
68
|
+
## Inbox decisions
|
|
69
|
+
|
|
70
|
+
"Mark all read" and "Dismiss all" never touch a decision, so clearing a backlog
|
|
71
|
+
of notices can't bury an approval waiting on a yes. `isDecisionCard(card)` is
|
|
72
|
+
that rule: the Signal API follows it when it writes, and the dashboard follows
|
|
73
|
+
it to show the result before the write, so the two can't drift apart.
|
|
74
|
+
|
|
75
|
+
A card is a decision when it is:
|
|
76
|
+
|
|
77
|
+
- an approval (insight type `approval_needed`, `APPROVAL_NEEDED_INSIGHT_TYPE`);
|
|
78
|
+
- the ledger's unconfirmed-action card (`ledger_unconfirmed_action`,
|
|
79
|
+
`LEDGER_UNCONFIRMED_INSIGHT_TYPE`): Signal started something it never saw
|
|
80
|
+
finish, so a person has to check it. It's raised once per claim, so a bulk
|
|
81
|
+
dismissal that took it would leave nobody told. It carries no proposal, so
|
|
82
|
+
there's nothing to approve or reject on it; or
|
|
83
|
+
- any card carrying a ledger proposal (`data.proposal_id` set).
|
|
84
|
+
|
|
85
|
+
`DECISION_INSIGHT_TYPES` is the first two, for the callers that need them as a
|
|
86
|
+
list (a database filter that leaves them out of a bulk write, for one).
|
|
87
|
+
`isDecisionCard` takes any object with an `insight_type` string and an optional
|
|
88
|
+
`data` object, so a stored row and the dashboard's inbox item both fit. It reads
|
|
89
|
+
`data.proposal_id` the way the database reads `data->>proposal_id`: a missing
|
|
90
|
+
key, a JSON `null` and a `null` `data` mean no proposal, and any other value,
|
|
91
|
+
an empty string included, means one.
|
|
92
|
+
|
|
66
93
|
## Schema placeholders
|
|
67
94
|
|
|
68
95
|
`schema-placeholders` finds the parts of a JSON-LD value that a template left
|
|
@@ -794,6 +821,14 @@ branding.mergePublicationBrandingPatch(saved, { identity: { logos: { dark: null
|
|
|
794
821
|
`{ version: 1 }`, and a change that sets nothing returns the profile as it
|
|
795
822
|
was. **`mergePublicationBrandingLayers`** is the same leaf-wise merge for
|
|
796
823
|
resolving layers.
|
|
824
|
+
- **Publication logo roles** are optional `identity.squareLogoUrl` and
|
|
825
|
+
`identity.fullLogoUrl`. Use the first for a square publication mark and the
|
|
826
|
+
second for its full/display logo. Both follow the same URL policy as other
|
|
827
|
+
logo fields. Set either leaf to `null` in a patch to clear that override
|
|
828
|
+
and restore inheritance. `identity.logos.default/light/dark` remain theme
|
|
829
|
+
variants, `identity.wordmark` stays text, and `publisherLogoUrl` is publisher
|
|
830
|
+
attribution. Omitting the new leaves preserves existing profiles; the
|
|
831
|
+
validator doesn't infer a square mark or select an effective display logo.
|
|
797
832
|
- **Refusals name the field.** A `PublicationBrandingValidationError` carries
|
|
798
833
|
the path (`publication_branding.platforms.email.colors.primary`) and says
|
|
799
834
|
what's allowed. A charter set on a channel override is a plain `Error`: a
|
|
@@ -882,3 +917,15 @@ a shape bumps the major and that contract's own `*_VERSION` constant.
|
|
|
882
917
|
### Editorial media
|
|
883
918
|
|
|
884
919
|
`@sonordev/contracts/editorial-media` exports `resolveArticlePhotoPlacements(html, photos)`, returning section targets and a status/offset for each explicit placement. Both the editor and article renderer use this contract; a heading with no eligible prose is reported as `no_paragraph`, and missing/ambiguous anchors never drift to another section. `editorialTextId` creates content identities for explicit takeaway assignments. Source-image format, size and completed-art exclusions are shared by the picker and resolver.
|
|
920
|
+
|
|
921
|
+
## The funnels contract
|
|
922
|
+
|
|
923
|
+
Import from `@sonordev/contracts/funnels`. `createFunnelDraft(template)` provides neutral defaults for `newsletter`, `requested_guide` and `inquiry`. `parseFunnelDraftDefinition` accepts bounded incomplete drafts so each setup step can save. `parseFunnelDefinition` requires a launch-ready structural definition. Resource ownership, current permission, route installation and launch authorization remain server checks.
|
|
924
|
+
|
|
925
|
+
Use `parseFunnelCreateArgs`, `parseFunnelUpdateArgs`, `parseFunnelRevisionArgs`, `parseFunnelLaunchArgs`, `parseFunnelLifecycleArgs` and `parseFunnelRetryArgs` at command boundaries. `parseFunnelDraftArgs(value, mode)` requires an explicit create/update mode. Rollback uses `parseFunnelLifecycleArgs(value, 'rollback')`. Matching exported JSON schemas describe the closed structural shapes; runtime parsers also enforce semantic checks such as matching form references and managed paths.
|
|
926
|
+
|
|
927
|
+
`applyFunnelBrandOverride` takes an already resolved brand, an allowlisted presentation patch and an API-authorized asset map. Null leaves restore inheritance. It returns the resulting brand, overridden field paths and asset references. It doesn't fetch assets, resolve business precedence or calculate authoritative hashes. `parseFunnelBrandSnapshot` validates the captured presentation and provenance.
|
|
928
|
+
|
|
929
|
+
`parsePublishedFunnelPage` refuses private definition fields and unsafe asset URLs. It supports bounded text paragraphs and bullet lists, with a managed form slug and canonical campaign identity. `managedFunnelPath` reserves `/f/:slug`; a consuming site must opt in and verify its actual routes.
|
|
930
|
+
|
|
931
|
+
`parseFunnelReadiness`, `parseFunnelOperationReceipt` and the detail/catalog/preview parsers validate bounded response envelopes. Operation receipts bind the exact command target and approved action, and always report zero messages sent by the configuration command. Preview and published-version detail parsers preserve the exact launch or rollback evidence.
|
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
import { type PublicationColors } from './publication-branding';
|
|
2
|
+
export { normalizeCampaignSlug } from './campaign-normalize';
|
|
3
|
+
export declare const FUNNEL_SCHEMA_VERSION: 1;
|
|
4
|
+
export declare const MANAGED_FUNNEL_PATH_PREFIX: '/f';
|
|
5
|
+
export declare const FUNNEL_TEMPLATES: readonly ['newsletter', 'requested_guide', 'inquiry'];
|
|
6
|
+
export type FunnelTemplate = typeof FUNNEL_TEMPLATES[number];
|
|
7
|
+
export type FunnelLifecycle = 'draft' | 'active' | 'paused';
|
|
8
|
+
export type FunnelHealth = 'healthy' | 'waiting' | 'needs_attention' | 'unknown';
|
|
9
|
+
export type FunnelBodyBlock = {
|
|
10
|
+
type: 'paragraph';
|
|
11
|
+
text: string;
|
|
12
|
+
} | {
|
|
13
|
+
type: 'bullets';
|
|
14
|
+
items: string[];
|
|
15
|
+
};
|
|
16
|
+
export interface FunnelBrandPresentation {
|
|
17
|
+
displayName: string;
|
|
18
|
+
tagline: string;
|
|
19
|
+
fullLogoUrl: string | null;
|
|
20
|
+
squareLogoUrl: string | null;
|
|
21
|
+
colors: Required<PublicationColors>;
|
|
22
|
+
typography: {
|
|
23
|
+
headingFamily: string;
|
|
24
|
+
bodyFamily: string;
|
|
25
|
+
};
|
|
26
|
+
scheme: 'light' | 'dark';
|
|
27
|
+
}
|
|
28
|
+
export interface FunnelBrandOverride {
|
|
29
|
+
displayName?: string | null;
|
|
30
|
+
tagline?: string | null;
|
|
31
|
+
fullLogoAssetId?: string | null;
|
|
32
|
+
squareLogoAssetId?: string | null;
|
|
33
|
+
colors?: {
|
|
34
|
+
[K in keyof PublicationColors]?: string | null;
|
|
35
|
+
};
|
|
36
|
+
typography?: {
|
|
37
|
+
headingFamily?: string | null;
|
|
38
|
+
bodyFamily?: string | null;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export type FunnelBrandSource = 'project' | 'brand_profile' | 'site_design' | 'publication_branding' | 'platforms.publication' | 'platforms.email' | 'funnel_override' | 'default';
|
|
42
|
+
export interface FunnelBrandSnapshot {
|
|
43
|
+
page: FunnelBrandPresentation;
|
|
44
|
+
email: FunnelBrandPresentation;
|
|
45
|
+
provenance: Record<string, FunnelBrandSource>;
|
|
46
|
+
assetRefs: {
|
|
47
|
+
id: string | null;
|
|
48
|
+
version: string | null;
|
|
49
|
+
}[];
|
|
50
|
+
capturedAt: string;
|
|
51
|
+
fingerprint: string;
|
|
52
|
+
}
|
|
53
|
+
export type FunnelStep = {
|
|
54
|
+
id: string;
|
|
55
|
+
type: 'wait';
|
|
56
|
+
durationSeconds: number;
|
|
57
|
+
} | {
|
|
58
|
+
id: string;
|
|
59
|
+
type: 'send_email';
|
|
60
|
+
templateId: string;
|
|
61
|
+
purpose: 'requested_fulfillment' | 'marketing';
|
|
62
|
+
guideAssetId?: string | null;
|
|
63
|
+
} | {
|
|
64
|
+
id: string;
|
|
65
|
+
type: 'add_tag';
|
|
66
|
+
target: 'subscriber';
|
|
67
|
+
tag: string;
|
|
68
|
+
} | {
|
|
69
|
+
id: string;
|
|
70
|
+
type: 'create_task';
|
|
71
|
+
ownerContactId: string;
|
|
72
|
+
title: string;
|
|
73
|
+
dueAfterSeconds: number;
|
|
74
|
+
};
|
|
75
|
+
export interface FunnelDefinition {
|
|
76
|
+
schemaVersion: 1;
|
|
77
|
+
name: string;
|
|
78
|
+
template: FunnelTemplate;
|
|
79
|
+
site: string;
|
|
80
|
+
entry: {
|
|
81
|
+
formId: string;
|
|
82
|
+
mode: 'future_accepted_form_only';
|
|
83
|
+
};
|
|
84
|
+
goal: {
|
|
85
|
+
type: 'newsletter_confirmed' | 'guide_requested' | 'inquiry_received' | 'booking_confirmed';
|
|
86
|
+
bookingTypeId?: string | null;
|
|
87
|
+
};
|
|
88
|
+
page: {
|
|
89
|
+
mode: 'existing' | 'managed';
|
|
90
|
+
path: string;
|
|
91
|
+
slug?: string | null;
|
|
92
|
+
headline: string;
|
|
93
|
+
body: FunnelBodyBlock[];
|
|
94
|
+
formId: string;
|
|
95
|
+
offerAssetId?: string | null;
|
|
96
|
+
};
|
|
97
|
+
permission: {
|
|
98
|
+
mode: 'requested_only' | 'marketing_opt_in';
|
|
99
|
+
optInFieldSlug?: string | null;
|
|
100
|
+
};
|
|
101
|
+
brandOverride?: FunnelBrandOverride;
|
|
102
|
+
steps: FunnelStep[];
|
|
103
|
+
exits: {
|
|
104
|
+
unsubscribe: true;
|
|
105
|
+
manualStop: true;
|
|
106
|
+
confirmedBooking?: {
|
|
107
|
+
enabled: boolean;
|
|
108
|
+
bookingTypeId: string | null;
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export interface FunnelIssue {
|
|
113
|
+
code: string;
|
|
114
|
+
path: string;
|
|
115
|
+
message: string;
|
|
116
|
+
fixRef?: {
|
|
117
|
+
section: string;
|
|
118
|
+
field?: string;
|
|
119
|
+
resourceId?: string;
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export interface FunnelReadiness {
|
|
123
|
+
definitionRevision: number;
|
|
124
|
+
brandFingerprint: string;
|
|
125
|
+
resourceFingerprint: string;
|
|
126
|
+
ready: boolean;
|
|
127
|
+
blockers: FunnelIssue[];
|
|
128
|
+
warnings: FunnelIssue[];
|
|
129
|
+
capabilities: {
|
|
130
|
+
managedPage: boolean;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export interface PublishedFunnelPage {
|
|
134
|
+
schemaVersion: 1;
|
|
135
|
+
id: string;
|
|
136
|
+
versionId: string;
|
|
137
|
+
revision: number;
|
|
138
|
+
site: string;
|
|
139
|
+
path: string;
|
|
140
|
+
content: {
|
|
141
|
+
headline: string;
|
|
142
|
+
body: FunnelBodyBlock[];
|
|
143
|
+
offer?: {
|
|
144
|
+
label: string;
|
|
145
|
+
href: string;
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
brand: FunnelBrandPresentation;
|
|
149
|
+
conversion: {
|
|
150
|
+
formSlug: string;
|
|
151
|
+
};
|
|
152
|
+
tracking: {
|
|
153
|
+
campaignSlug: string;
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
export interface FunnelOperationReceipt {
|
|
157
|
+
operationId: string;
|
|
158
|
+
projectId: string;
|
|
159
|
+
orgId: string;
|
|
160
|
+
funnelId: string;
|
|
161
|
+
operation: 'create' | 'update' | 'launch' | 'pause' | 'resume' | 'retry' | 'rollback';
|
|
162
|
+
idempotencyKey: string;
|
|
163
|
+
requestDigest: string;
|
|
164
|
+
versionId: string | null;
|
|
165
|
+
lifecycle: FunnelLifecycle;
|
|
166
|
+
replayed: boolean;
|
|
167
|
+
armedFutureEntrantsOnly: true;
|
|
168
|
+
enrolledExisting: 0;
|
|
169
|
+
messagesSent: 0;
|
|
170
|
+
pagePublication: 'not_requested' | 'published' | 'revalidation_pending';
|
|
171
|
+
health: FunnelHealth;
|
|
172
|
+
revision: number;
|
|
173
|
+
commandState: 'committed';
|
|
174
|
+
funnel: FunnelDetail | null;
|
|
175
|
+
actor: string;
|
|
176
|
+
armedAt: string | null;
|
|
177
|
+
effectScope: 'configuration_only' | 'safe_retry_armed';
|
|
178
|
+
target: FunnelCommandTarget;
|
|
179
|
+
ledgerProposalId: string;
|
|
180
|
+
}
|
|
181
|
+
export interface FunnelCommandTarget {
|
|
182
|
+
funnelId: string | null;
|
|
183
|
+
enrollmentId: string | null;
|
|
184
|
+
executionId: string | null;
|
|
185
|
+
targetVersionId: string | null;
|
|
186
|
+
}
|
|
187
|
+
export interface FunnelLaunchArgs {
|
|
188
|
+
expectedRevision: number;
|
|
189
|
+
previewId: string;
|
|
190
|
+
idempotencyKey: string;
|
|
191
|
+
confirm: true;
|
|
192
|
+
}
|
|
193
|
+
export interface FunnelLifecycleArgs {
|
|
194
|
+
expectedActiveVersionId: string;
|
|
195
|
+
idempotencyKey: string;
|
|
196
|
+
confirm: true;
|
|
197
|
+
targetVersionId?: string;
|
|
198
|
+
}
|
|
199
|
+
/** Small closed JSON-schema vocabulary shared by the exported schema and its runtime parser. */
|
|
200
|
+
type Shape = {
|
|
201
|
+
type?: 'object' | 'array' | 'string' | 'integer' | 'boolean' | 'null';
|
|
202
|
+
properties?: Record<string, Shape>;
|
|
203
|
+
required?: readonly string[];
|
|
204
|
+
additionalProperties?: false;
|
|
205
|
+
items?: Shape;
|
|
206
|
+
minItems?: number;
|
|
207
|
+
maxItems?: number;
|
|
208
|
+
minLength?: number;
|
|
209
|
+
maxLength?: number;
|
|
210
|
+
pattern?: string;
|
|
211
|
+
minimum?: number;
|
|
212
|
+
maximum?: number;
|
|
213
|
+
enum?: readonly unknown[];
|
|
214
|
+
const?: unknown;
|
|
215
|
+
oneOf?: readonly Shape[];
|
|
216
|
+
};
|
|
217
|
+
export type FunnelDraftStep = Extract<FunnelStep, {
|
|
218
|
+
type: 'wait' | 'add_tag';
|
|
219
|
+
}> | (Omit<Extract<FunnelStep, {
|
|
220
|
+
type: 'send_email';
|
|
221
|
+
}>, 'templateId' | 'guideAssetId'> & {
|
|
222
|
+
templateId: string | null;
|
|
223
|
+
guideAssetId?: string | null;
|
|
224
|
+
}) | (Omit<Extract<FunnelStep, {
|
|
225
|
+
type: 'create_task';
|
|
226
|
+
}>, 'ownerContactId'> & {
|
|
227
|
+
ownerContactId: string | null;
|
|
228
|
+
});
|
|
229
|
+
export interface FunnelDraftDefinition extends Omit<FunnelDefinition, 'site' | 'entry' | 'page' | 'steps'> {
|
|
230
|
+
site: string | null;
|
|
231
|
+
entry: {
|
|
232
|
+
formId: string | null;
|
|
233
|
+
mode: 'future_accepted_form_only';
|
|
234
|
+
};
|
|
235
|
+
page: Omit<FunnelDefinition['page'], 'path' | 'formId'> & {
|
|
236
|
+
path: string | null;
|
|
237
|
+
formId: string | null;
|
|
238
|
+
};
|
|
239
|
+
steps: FunnelDraftStep[];
|
|
240
|
+
}
|
|
241
|
+
export declare const FUNNEL_DRAFT_DEFINITION_JSON_SCHEMA: {
|
|
242
|
+
readonly type?: 'object' | 'array' | 'string' | 'integer' | 'boolean' | 'null';
|
|
243
|
+
readonly properties?: Record<string, Shape>;
|
|
244
|
+
readonly required?: readonly string[];
|
|
245
|
+
readonly additionalProperties?: false;
|
|
246
|
+
readonly items?: Shape;
|
|
247
|
+
readonly minItems?: number;
|
|
248
|
+
readonly maxItems?: number;
|
|
249
|
+
readonly minLength?: number;
|
|
250
|
+
readonly maxLength?: number;
|
|
251
|
+
readonly pattern?: string;
|
|
252
|
+
readonly minimum?: number;
|
|
253
|
+
readonly maximum?: number;
|
|
254
|
+
readonly enum?: readonly unknown[];
|
|
255
|
+
readonly const?: unknown;
|
|
256
|
+
readonly oneOf?: readonly Shape[];
|
|
257
|
+
readonly $schema: 'http://json-schema.org/draft-07/schema#';
|
|
258
|
+
};
|
|
259
|
+
export declare function parseFunnelDraftDefinition(value: unknown): FunnelDraftDefinition;
|
|
260
|
+
export declare function createFunnelDraft(template: FunnelTemplate): FunnelDraftDefinition;
|
|
261
|
+
export declare const FUNNEL_DEFINITION_JSON_SCHEMA: {
|
|
262
|
+
readonly type?: 'object' | 'array' | 'string' | 'integer' | 'boolean' | 'null';
|
|
263
|
+
readonly properties?: Record<string, Shape>;
|
|
264
|
+
readonly required?: readonly string[];
|
|
265
|
+
readonly additionalProperties?: false;
|
|
266
|
+
readonly items?: Shape;
|
|
267
|
+
readonly minItems?: number;
|
|
268
|
+
readonly maxItems?: number;
|
|
269
|
+
readonly minLength?: number;
|
|
270
|
+
readonly maxLength?: number;
|
|
271
|
+
readonly pattern?: string;
|
|
272
|
+
readonly minimum?: number;
|
|
273
|
+
readonly maximum?: number;
|
|
274
|
+
readonly enum?: readonly unknown[];
|
|
275
|
+
readonly const?: unknown;
|
|
276
|
+
readonly oneOf?: readonly Shape[];
|
|
277
|
+
readonly $schema: 'http://json-schema.org/draft-07/schema#';
|
|
278
|
+
};
|
|
279
|
+
export declare const PUBLISHED_FUNNEL_PAGE_JSON_SCHEMA: {
|
|
280
|
+
readonly type?: 'object' | 'array' | 'string' | 'integer' | 'boolean' | 'null';
|
|
281
|
+
readonly properties?: Record<string, Shape>;
|
|
282
|
+
readonly required?: readonly string[];
|
|
283
|
+
readonly additionalProperties?: false;
|
|
284
|
+
readonly items?: Shape;
|
|
285
|
+
readonly minItems?: number;
|
|
286
|
+
readonly maxItems?: number;
|
|
287
|
+
readonly minLength?: number;
|
|
288
|
+
readonly maxLength?: number;
|
|
289
|
+
readonly pattern?: string;
|
|
290
|
+
readonly minimum?: number;
|
|
291
|
+
readonly maximum?: number;
|
|
292
|
+
readonly enum?: readonly unknown[];
|
|
293
|
+
readonly const?: unknown;
|
|
294
|
+
readonly oneOf?: readonly Shape[];
|
|
295
|
+
readonly $schema: 'http://json-schema.org/draft-07/schema#';
|
|
296
|
+
};
|
|
297
|
+
export declare const FUNNEL_LAUNCH_JSON_SCHEMA: Shape;
|
|
298
|
+
export declare const FUNNEL_LIFECYCLE_JSON_SCHEMA: Shape;
|
|
299
|
+
export declare const FUNNEL_ROLLBACK_JSON_SCHEMA: Shape;
|
|
300
|
+
export declare class FunnelValidationError extends Error {
|
|
301
|
+
readonly path: string;
|
|
302
|
+
constructor(path: string, reason: string);
|
|
303
|
+
}
|
|
304
|
+
/** No DNS/network access. Consumers fetching URLs still enforce their connection policy. */
|
|
305
|
+
export declare function isSafeFunnelPublicUrl(value: string): boolean;
|
|
306
|
+
export declare function managedFunnelPath(value: string): string;
|
|
307
|
+
export declare function parseFunnelDefinition(value: unknown): FunnelDefinition;
|
|
308
|
+
export declare function parsePublishedFunnelPage(value: unknown): PublishedFunnelPage;
|
|
309
|
+
export declare function parseFunnelLaunchArgs(value: unknown): FunnelLaunchArgs;
|
|
310
|
+
export declare function parseFunnelLifecycleArgs(value: unknown, operation?: 'pause' | 'resume' | 'rollback'): FunnelLifecycleArgs;
|
|
311
|
+
/** Apply presentation-only overrides to an already resolved canonical brand. Null means inherit. */
|
|
312
|
+
export declare function applyFunnelBrandOverride(inherited: FunnelBrandPresentation, patch?: FunnelBrandOverride, resolvedAssets?: Record<string, {
|
|
313
|
+
url: string;
|
|
314
|
+
version?: string;
|
|
315
|
+
}>): {
|
|
316
|
+
brand: FunnelBrandPresentation;
|
|
317
|
+
overridePaths: string[];
|
|
318
|
+
assetRefs: {
|
|
319
|
+
id: string;
|
|
320
|
+
version: string | null;
|
|
321
|
+
}[];
|
|
322
|
+
};
|
|
323
|
+
export interface FunnelCreateArgs {
|
|
324
|
+
idempotencyKey: string;
|
|
325
|
+
definition: FunnelDraftDefinition;
|
|
326
|
+
}
|
|
327
|
+
export interface FunnelUpdateArgs extends FunnelCreateArgs {
|
|
328
|
+
expectedRevision: number;
|
|
329
|
+
}
|
|
330
|
+
export declare const FUNNEL_CREATE_JSON_SCHEMA: Shape;
|
|
331
|
+
export declare const FUNNEL_UPDATE_JSON_SCHEMA: Shape;
|
|
332
|
+
export declare function parseFunnelCreateArgs(value: unknown): FunnelCreateArgs;
|
|
333
|
+
export declare function parseFunnelUpdateArgs(value: unknown): FunnelUpdateArgs;
|
|
334
|
+
export interface FunnelPreview {
|
|
335
|
+
previewId: string;
|
|
336
|
+
versionId: string;
|
|
337
|
+
expiresAt: string;
|
|
338
|
+
definitionRevision: number;
|
|
339
|
+
brand: FunnelBrandSnapshot;
|
|
340
|
+
readiness: FunnelReadiness;
|
|
341
|
+
page: PublishedFunnelPage | null;
|
|
342
|
+
launchEvidence: FunnelLaunchEvidence;
|
|
343
|
+
emailPreviews: {
|
|
344
|
+
stepId: string;
|
|
345
|
+
ready: boolean;
|
|
346
|
+
subject: string | null;
|
|
347
|
+
contentHtml: string | null;
|
|
348
|
+
issues: FunnelIssue[];
|
|
349
|
+
}[];
|
|
350
|
+
}
|
|
351
|
+
export interface FunnelSummary {
|
|
352
|
+
id: string;
|
|
353
|
+
name: string;
|
|
354
|
+
template: FunnelTemplate;
|
|
355
|
+
site: string | null;
|
|
356
|
+
revision: number;
|
|
357
|
+
activeVersionId: string | null;
|
|
358
|
+
lifecycle: FunnelLifecycle;
|
|
359
|
+
health: FunnelHealth;
|
|
360
|
+
hasDraftChanges: boolean;
|
|
361
|
+
counts: {
|
|
362
|
+
entered: number;
|
|
363
|
+
waiting: number;
|
|
364
|
+
running: number;
|
|
365
|
+
completed: number;
|
|
366
|
+
exited: number;
|
|
367
|
+
needsAttention: number;
|
|
368
|
+
};
|
|
369
|
+
countsBasis: 'all_versions_all_time';
|
|
370
|
+
lastEventAt: string | null;
|
|
371
|
+
}
|
|
372
|
+
export type FunnelDraftArgs = FunnelCreateArgs | FunnelUpdateArgs;
|
|
373
|
+
/** Select create/update explicitly at the HTTP boundary; a missing revision never downgrades an update. */
|
|
374
|
+
export declare function parseFunnelDraftArgs(value: unknown, mode: 'create' | 'update'): FunnelDraftArgs;
|
|
375
|
+
export interface FunnelRevisionArgs {
|
|
376
|
+
expectedRevision: number;
|
|
377
|
+
}
|
|
378
|
+
export interface FunnelRetryArgs {
|
|
379
|
+
expectedAttempt: number;
|
|
380
|
+
idempotencyKey: string;
|
|
381
|
+
confirm: true;
|
|
382
|
+
}
|
|
383
|
+
export declare const FUNNEL_REVISION_JSON_SCHEMA: Shape;
|
|
384
|
+
export declare const FUNNEL_RETRY_JSON_SCHEMA: Shape;
|
|
385
|
+
export declare const FUNNEL_DRAFT_JSON_SCHEMAS: {
|
|
386
|
+
readonly create: Shape;
|
|
387
|
+
readonly update: Shape;
|
|
388
|
+
};
|
|
389
|
+
export declare function parseFunnelRevisionArgs(value: unknown): FunnelRevisionArgs;
|
|
390
|
+
export declare function parseFunnelRetryArgs(value: unknown): FunnelRetryArgs;
|
|
391
|
+
export declare const FUNNEL_READINESS_JSON_SCHEMA: Shape;
|
|
392
|
+
export declare function parseFunnelReadiness(value: unknown): FunnelReadiness;
|
|
393
|
+
export interface FunnelDetail {
|
|
394
|
+
id: string;
|
|
395
|
+
revision: number;
|
|
396
|
+
definition: FunnelDraftDefinition;
|
|
397
|
+
activeVersionId: string | null;
|
|
398
|
+
lifecycle: FunnelLifecycle;
|
|
399
|
+
health: FunnelHealth;
|
|
400
|
+
hasDraftChanges: boolean;
|
|
401
|
+
readiness: FunnelReadiness;
|
|
402
|
+
}
|
|
403
|
+
export declare const FUNNEL_DETAIL_JSON_SCHEMA: Shape;
|
|
404
|
+
export declare function parseFunnelDetail(value: unknown): FunnelDetail;
|
|
405
|
+
export declare const FUNNEL_OPERATION_RECEIPT_JSON_SCHEMA: Shape;
|
|
406
|
+
export declare function parseFunnelOperationReceipt(value: unknown): FunnelOperationReceipt;
|
|
407
|
+
export declare const FUNNEL_BRAND_SNAPSHOT_JSON_SCHEMA: Shape;
|
|
408
|
+
export declare function parseFunnelBrandSnapshot(value: unknown): FunnelBrandSnapshot;
|
|
409
|
+
export interface FunnelCatalogItem {
|
|
410
|
+
id: string;
|
|
411
|
+
label: string;
|
|
412
|
+
}
|
|
413
|
+
export interface FunnelFormChoice extends FunnelCatalogItem {
|
|
414
|
+
slug: string;
|
|
415
|
+
site: string | null;
|
|
416
|
+
formType: string;
|
|
417
|
+
active: boolean;
|
|
418
|
+
newsletterListIds: string[];
|
|
419
|
+
fields: {
|
|
420
|
+
slug: string;
|
|
421
|
+
type: string;
|
|
422
|
+
label: string;
|
|
423
|
+
required: boolean;
|
|
424
|
+
}[];
|
|
425
|
+
}
|
|
426
|
+
export interface FunnelListChoice extends FunnelCatalogItem {
|
|
427
|
+
active: boolean;
|
|
428
|
+
doubleOptIn: boolean;
|
|
429
|
+
}
|
|
430
|
+
export interface FunnelEmailTemplateChoice extends FunnelCatalogItem {
|
|
431
|
+
subject: string;
|
|
432
|
+
updatedAt: string | null;
|
|
433
|
+
}
|
|
434
|
+
export interface FunnelAssetChoice extends FunnelCatalogItem {
|
|
435
|
+
mimeType: string;
|
|
436
|
+
sizeBytes: number;
|
|
437
|
+
visibility: 'public';
|
|
438
|
+
publicUrl: string;
|
|
439
|
+
version: string | null;
|
|
440
|
+
}
|
|
441
|
+
export interface FunnelOwnerChoice extends FunnelCatalogItem {
|
|
442
|
+
active: true;
|
|
443
|
+
}
|
|
444
|
+
export interface FunnelBookingChoice extends FunnelCatalogItem {
|
|
445
|
+
site: string | null;
|
|
446
|
+
active: true;
|
|
447
|
+
requiresConfirmation: boolean;
|
|
448
|
+
}
|
|
449
|
+
export interface FunnelChoicePage<T> {
|
|
450
|
+
items: T[];
|
|
451
|
+
nextCursor: string | null;
|
|
452
|
+
}
|
|
453
|
+
export interface FunnelCatalog {
|
|
454
|
+
templates: {
|
|
455
|
+
id: FunnelTemplate;
|
|
456
|
+
label: string;
|
|
457
|
+
defaults: FunnelDraftDefinition;
|
|
458
|
+
}[];
|
|
459
|
+
sites: {
|
|
460
|
+
host: string;
|
|
461
|
+
label: string;
|
|
462
|
+
}[];
|
|
463
|
+
brand: FunnelBrandSnapshot | null;
|
|
464
|
+
choices: {
|
|
465
|
+
forms?: FunnelChoicePage<FunnelFormChoice>;
|
|
466
|
+
lists?: FunnelChoicePage<FunnelListChoice>;
|
|
467
|
+
emailTemplates?: FunnelChoicePage<FunnelEmailTemplateChoice>;
|
|
468
|
+
assets?: FunnelChoicePage<FunnelAssetChoice>;
|
|
469
|
+
owners?: FunnelChoicePage<FunnelOwnerChoice>;
|
|
470
|
+
bookingTypes?: FunnelChoicePage<FunnelBookingChoice>;
|
|
471
|
+
};
|
|
472
|
+
capabilities: {
|
|
473
|
+
managedPage: boolean;
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
export declare const FUNNEL_CATALOG_JSON_SCHEMA: Shape;
|
|
477
|
+
export declare function parseFunnelCatalog(value: unknown): FunnelCatalog;
|
|
478
|
+
export interface FunnelLaunchEvidence {
|
|
479
|
+
projectId: string;
|
|
480
|
+
orgId: string;
|
|
481
|
+
funnelId: string;
|
|
482
|
+
previewId: string;
|
|
483
|
+
versionId: string;
|
|
484
|
+
definitionRevision: number;
|
|
485
|
+
definitionDigest: string;
|
|
486
|
+
brandFingerprint: string;
|
|
487
|
+
resourceFingerprint: string;
|
|
488
|
+
entryDigest: string;
|
|
489
|
+
permissionDigest: string;
|
|
490
|
+
expiresAt: string;
|
|
491
|
+
}
|
|
492
|
+
export interface FunnelRollbackEvidence {
|
|
493
|
+
orgId: string;
|
|
494
|
+
projectId: string;
|
|
495
|
+
funnelId: string;
|
|
496
|
+
targetVersionId: string;
|
|
497
|
+
definitionRevision: number;
|
|
498
|
+
definitionDigest: string;
|
|
499
|
+
brandFingerprint: string;
|
|
500
|
+
resourceFingerprint: string;
|
|
501
|
+
}
|
|
502
|
+
export declare const FUNNEL_LAUNCH_EVIDENCE_JSON_SCHEMA: Shape;
|
|
503
|
+
export declare function parseFunnelLaunchEvidence(value: unknown): FunnelLaunchEvidence;
|
|
504
|
+
export declare const FUNNEL_ROLLBACK_EVIDENCE_JSON_SCHEMA: Shape;
|
|
505
|
+
export declare function parseFunnelRollbackEvidence(value: unknown): FunnelRollbackEvidence;
|
|
506
|
+
export declare const FUNNEL_PREVIEW_JSON_SCHEMA: Shape;
|
|
507
|
+
export declare function parseFunnelPreview(value: unknown): FunnelPreview;
|
|
508
|
+
export type FunnelEnrollmentState = 'waiting' | 'running' | 'completed' | 'exited' | 'needs_attention';
|
|
509
|
+
export interface FunnelEnrollmentSummary {
|
|
510
|
+
id: string;
|
|
511
|
+
versionId: string;
|
|
512
|
+
state: FunnelEnrollmentState;
|
|
513
|
+
effectiveStatus: FunnelEnrollmentState | 'paused';
|
|
514
|
+
source: {
|
|
515
|
+
submissionId: string;
|
|
516
|
+
acceptedAt: string;
|
|
517
|
+
};
|
|
518
|
+
subject: {
|
|
519
|
+
kind: 'subscriber' | 'contact' | 'submission';
|
|
520
|
+
id: string;
|
|
521
|
+
displayLabel: string;
|
|
522
|
+
};
|
|
523
|
+
enteredAt: string;
|
|
524
|
+
nextRunAt: string | null;
|
|
525
|
+
exitCode: string | null;
|
|
526
|
+
stepsCompleted: number;
|
|
527
|
+
stepsTotal: number;
|
|
528
|
+
lastReceiptAt: string | null;
|
|
529
|
+
}
|
|
530
|
+
export interface FunnelTimeline {
|
|
531
|
+
items: {
|
|
532
|
+
executionId: string;
|
|
533
|
+
stepId: string;
|
|
534
|
+
type: FunnelStep['type'];
|
|
535
|
+
state: 'pending' | 'claimed' | 'queued' | 'admitted' | 'succeeded' | 'skipped' | 'uncertain' | 'failed';
|
|
536
|
+
dueAt: string | null;
|
|
537
|
+
occurredAt: string | null;
|
|
538
|
+
attempt: number;
|
|
539
|
+
reasonCode: string | null;
|
|
540
|
+
message: string;
|
|
541
|
+
receipt: {
|
|
542
|
+
domain: 'campaign_send' | 'subscriber_tag' | 'crm_task' | 'wait';
|
|
543
|
+
id: string | null;
|
|
544
|
+
state: string;
|
|
545
|
+
} | null;
|
|
546
|
+
canRetry: boolean;
|
|
547
|
+
}[];
|
|
548
|
+
nextCursor: string | null;
|
|
549
|
+
exit: {
|
|
550
|
+
code: string;
|
|
551
|
+
sourceType: string;
|
|
552
|
+
sourceId: string | null;
|
|
553
|
+
occurredAt: string;
|
|
554
|
+
} | null;
|
|
555
|
+
}
|
|
556
|
+
export declare const FUNNEL_SUMMARY_JSON_SCHEMA: Shape;
|
|
557
|
+
export declare function parseFunnelSummary(value: unknown): FunnelSummary;
|
|
558
|
+
export declare const FUNNEL_ENROLLMENT_JSON_SCHEMA: Shape;
|
|
559
|
+
export declare function parseFunnelEnrollmentSummary(value: unknown): FunnelEnrollmentSummary;
|
|
560
|
+
export declare const FUNNEL_TIMELINE_JSON_SCHEMA: Shape;
|
|
561
|
+
export declare function parseFunnelTimeline(value: unknown): FunnelTimeline;
|
|
562
|
+
export interface FunnelPublishedVersion {
|
|
563
|
+
versionId: string;
|
|
564
|
+
definitionRevision: number;
|
|
565
|
+
publishedAt: string;
|
|
566
|
+
brandFingerprint: string;
|
|
567
|
+
resourceFingerprint: string;
|
|
568
|
+
active: boolean;
|
|
569
|
+
}
|
|
570
|
+
export interface FunnelPublishedVersionDetail extends FunnelPublishedVersion {
|
|
571
|
+
rollbackEvidence: FunnelRollbackEvidence;
|
|
572
|
+
}
|
|
573
|
+
export declare const FUNNEL_VERSION_JSON_SCHEMA: Shape;
|
|
574
|
+
export declare const FUNNEL_VERSION_DETAIL_JSON_SCHEMA: Shape;
|
|
575
|
+
export declare function parseFunnelPublishedVersion(value: unknown): FunnelPublishedVersion;
|
|
576
|
+
export declare function parseFunnelPublishedVersionDetail(value: unknown): FunnelPublishedVersionDetail;
|