@sonordev/contracts 2.12.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 +20 -1
- package/README.md +61 -5
- 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/portfolio.d.ts +6 -6
- package/dist/publication-branding.d.ts +10 -2
- package/dist/publication-branding.js +5 -3
- package/dist/publication-branding.mjs +5 -4
- 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,24 @@
|
|
|
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
|
+
|
|
18
|
+
## 2.13.0
|
|
19
|
+
|
|
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.
|
|
21
|
+
|
|
3
22
|
## 2.12.0
|
|
4
23
|
|
|
5
24
|
A minor release: `@sonordev/contracts/portfolio` now holds the structure of a case study's story. Every other entry is unchanged, and nothing already exported from `portfolio` changed.
|
|
@@ -7,7 +26,7 @@ A minor release: `@sonordev/contracts/portfolio` now holds the structure of a ca
|
|
|
7
26
|
- **The case study arc.** `CASE_STUDY_STAGES` names the four beats every case study reads in (`problem`, `build`, `proof`, `outcome`), `CASE_STUDY_STAGE_COPY` gives each a generic name and the question it answers, `PORTFOLIO_SECTION_TYPES` is every section type in canonical reading order, and `SECTION_STAGE` / `sectionStage` say which stage each belongs to. The section list used to be kept separately by the Sonor API and agency-site-kit; this is now the one copy.
|
|
8
27
|
- **Reading order.** `orderSections` always keeps the arc (hero, problem, build, proof, outcome, CTA) and keeps an agency's stored order only within a stage, so a reordered case study can no longer put its results above its challenges. `groupSectionsByStage` returns the beats a case study actually has, numbered in order. `PORTFOLIO_SECTION_ORDER` is derived from the list. Performance now reads before Results: the proof comes before the outcome.
|
|
9
28
|
- **Stage status.** `caseStudyStageStatus` reports all four stages, complete only with the stage's core section (`STAGE_CORE_SECTION`), with one sentence on what's missing.
|
|
10
|
-
- **Stage names.**
|
|
29
|
+
- **Stage names.** A site can call the four stages what it likes ("Heat", "Shape", "Temper", "Finish"); `CaseStudyStageNames` is that map, `stageName(names, stage)` reads it with the generic name as the fallback, `stageNamesProblems` validates it and `STAGE_NAME_MAX_CHARS` is the length limit. The ids never change; only what people see does.
|
|
11
30
|
- **The narrative.** `CaseStudyNarrative` is a case study's thesis and one lede per stage, written for the project rather than borrowed from the house. `narrativeProblems` validates one, `NARRATIVE_MAX_CHARS` is the length limit and `CASE_STUDY_NARRATIVE_JSON_SCHEMA` the same rule as a schema. An absent narrative passes.
|
|
12
31
|
- **The thread.** A challenge carries an `id` (`challengeId(n)`), a phase `resolves` challenges and a result `proves` them; `caseStudyFlowProblems` names every challenge nothing resolves or proves and every dangling reference, and passes content written before the thread existed.
|
|
13
32
|
|
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,17 @@ 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 |
|
|
43
|
+
|
|
44
|
+
## Focus closing artwork
|
|
45
|
+
|
|
46
|
+
`publication-branding` accepts `editorial.focusClosingBackground` at publication
|
|
47
|
+
and platform levels. `planned_photo` keeps the planned closing image;
|
|
48
|
+
`article_atmosphere` reuses the article's saved atmosphere with its real author
|
|
49
|
+
portrait. Omitting the field keeps existing behavior. A patch value of `null`
|
|
50
|
+
clears the override and restores inheritance. The article-atmosphere mode requires
|
|
51
|
+
both saved sources; it doesn't invent a portrait or substitute unrelated artwork.
|
|
41
52
|
|
|
42
53
|
## Public URL hosts
|
|
43
54
|
|
|
@@ -54,6 +65,31 @@ isn't proof a destination is public: the fetcher still validates the protocol,
|
|
|
54
65
|
credentials and port, checks the DNS addresses it connects to, and applies its
|
|
55
66
|
redirect policy on each hop. Those network protections stay in the APIs.
|
|
56
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
|
+
|
|
57
93
|
## Schema placeholders
|
|
58
94
|
|
|
59
95
|
`schema-placeholders` finds the parts of a JSON-LD value that a template left
|
|
@@ -785,6 +821,14 @@ branding.mergePublicationBrandingPatch(saved, { identity: { logos: { dark: null
|
|
|
785
821
|
`{ version: 1 }`, and a change that sets nothing returns the profile as it
|
|
786
822
|
was. **`mergePublicationBrandingLayers`** is the same leaf-wise merge for
|
|
787
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.
|
|
788
832
|
- **Refusals name the field.** A `PublicationBrandingValidationError` carries
|
|
789
833
|
the path (`publication_branding.platforms.email.colors.primary`) and says
|
|
790
834
|
what's allowed. A charter set on a channel override is a plain `Error`: a
|
|
@@ -829,10 +873,10 @@ the dashboard that edits it and every site that renders it.
|
|
|
829
873
|
only when the stage's core section (`STAGE_CORE_SECTION`: challenges,
|
|
830
874
|
strategy, performance, results) is present, with one sentence on what's
|
|
831
875
|
missing. The dashboard shows it beside the editor.
|
|
832
|
-
- **Stage names.** The ids are fixed; what people see isn't.
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
public portfolio config and the
|
|
876
|
+
- **Stage names.** The ids are fixed; what people see isn't. A site names
|
|
877
|
+
the four stages in the dashboard ("Heat", "Shape", "Temper", "Finish"),
|
|
878
|
+
per project like the rest of its branding, and `CaseStudyStageNames`
|
|
879
|
+
carries that map to the editor, the public portfolio config and the site. `stageName(names, stage)`
|
|
836
880
|
reads it with the generic name as the fallback; `stageNamesProblems`
|
|
837
881
|
validates it and `STAGE_NAME_MAX_CHARS` caps each name.
|
|
838
882
|
- **`CaseStudyNarrative`** is what the story says above its sections: one
|
|
@@ -873,3 +917,15 @@ a shape bumps the major and that contract's own `*_VERSION` constant.
|
|
|
873
917
|
### Editorial media
|
|
874
918
|
|
|
875
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.
|