@growth-labs/cms 0.6.6 → 0.6.7

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.
Files changed (72) hide show
  1. package/dist/engine/foundry-dispatch.d.ts.map +1 -1
  2. package/dist/engine/foundry-dispatch.js +36 -7
  3. package/dist/engine/foundry-dispatch.js.map +1 -1
  4. package/dist/engine/fronts-publish-intent.d.ts +15 -4
  5. package/dist/engine/fronts-publish-intent.d.ts.map +1 -1
  6. package/dist/engine/fronts-publish-intent.js +52 -19
  7. package/dist/engine/fronts-publish-intent.js.map +1 -1
  8. package/dist/engine/fronts-publish.d.ts +19 -0
  9. package/dist/engine/fronts-publish.d.ts.map +1 -1
  10. package/dist/engine/fronts-publish.js +158 -5
  11. package/dist/engine/fronts-publish.js.map +1 -1
  12. package/dist/engine/index.d.ts +1 -0
  13. package/dist/engine/index.d.ts.map +1 -1
  14. package/dist/engine/index.js +1 -0
  15. package/dist/engine/index.js.map +1 -1
  16. package/dist/engine/podcast-source.d.ts +28 -0
  17. package/dist/engine/podcast-source.d.ts.map +1 -0
  18. package/dist/engine/podcast-source.js +50 -0
  19. package/dist/engine/podcast-source.js.map +1 -0
  20. package/dist/engine/publication-current-guard.d.ts +10 -0
  21. package/dist/engine/publication-current-guard.d.ts.map +1 -0
  22. package/dist/engine/publication-current-guard.js +76 -0
  23. package/dist/engine/publication-current-guard.js.map +1 -0
  24. package/dist/engine/publication.d.ts +4 -0
  25. package/dist/engine/publication.d.ts.map +1 -1
  26. package/dist/engine/publication.js +13 -0
  27. package/dist/engine/publication.js.map +1 -1
  28. package/dist/engine/publisher.d.ts.map +1 -1
  29. package/dist/engine/publisher.js +20 -13
  30. package/dist/engine/publisher.js.map +1 -1
  31. package/dist/engine/soft-delete.js +2 -2
  32. package/dist/engine/soft-delete.js.map +1 -1
  33. package/dist/providers/types.d.ts +9 -3
  34. package/dist/providers/types.d.ts.map +1 -1
  35. package/dist/providers/types.js.map +1 -1
  36. package/dist/routes/content.d.ts +2 -0
  37. package/dist/routes/content.d.ts.map +1 -1
  38. package/dist/routes/content.js +1 -1
  39. package/dist/routes/content.js.map +1 -1
  40. package/dist/routes/fronts-publish.d.ts +2 -2
  41. package/dist/routes/fronts-publish.d.ts.map +1 -1
  42. package/dist/routes/fronts-publish.js +52 -23
  43. package/dist/routes/fronts-publish.js.map +1 -1
  44. package/dist/routes/index.js +1 -1
  45. package/dist/routes/index.js.map +1 -1
  46. package/dist/schema/layout.d.ts +2 -2
  47. package/dist/ui/api/_content-config.d.ts.map +1 -1
  48. package/dist/ui/api/_content-config.js +1 -0
  49. package/dist/ui/api/_content-config.js.map +1 -1
  50. package/dist/ui/api/fronts/publish-callback.d.ts.map +1 -1
  51. package/dist/ui/api/fronts/publish-callback.js +2 -6
  52. package/dist/ui/api/fronts/publish-callback.js.map +1 -1
  53. package/dist/ui/api/fronts/schedule.d.ts.map +1 -1
  54. package/dist/ui/api/fronts/schedule.js +2 -6
  55. package/dist/ui/api/fronts/schedule.js.map +1 -1
  56. package/package.json +1 -1
  57. package/src/engine/foundry-dispatch.ts +67 -12
  58. package/src/engine/fronts-publish-intent.ts +78 -26
  59. package/src/engine/fronts-publish.ts +223 -5
  60. package/src/engine/index.ts +4 -0
  61. package/src/engine/podcast-source.ts +82 -0
  62. package/src/engine/publication-current-guard.ts +87 -0
  63. package/src/engine/publication.ts +23 -0
  64. package/src/engine/publisher.ts +21 -13
  65. package/src/engine/soft-delete.ts +2 -2
  66. package/src/providers/types.ts +6 -1
  67. package/src/routes/content.ts +3 -1
  68. package/src/routes/fronts-publish.ts +63 -25
  69. package/src/routes/index.ts +1 -1
  70. package/src/ui/api/_content-config.ts +2 -0
  71. package/src/ui/api/fronts/publish-callback.ts +2 -8
  72. package/src/ui/api/fronts/schedule.ts +2 -8
@@ -144,7 +144,7 @@ export function createCmsRoutes(config) {
144
144
  authz: config.authz,
145
145
  activeWorkspaceId: config.activeWorkspaceId,
146
146
  }),
147
- frontsPublish: createFrontsPublishRoutes(base),
147
+ frontsPublish: createFrontsPublishRoutes(config),
148
148
  foundryPublish: createFoundryPublishRoutes(config),
149
149
  };
150
150
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":"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"}
@@ -85,6 +85,7 @@ export declare const layoutEnvelopeSchema: z.ZodObject<{
85
85
  }, z.ZodTypeAny, "passthrough">>, "many">>>;
86
86
  }, z.ZodTypeAny, "passthrough">>;
87
87
  }, "strip", z.ZodTypeAny, {
88
+ version: 1;
88
89
  data: {
89
90
  content: z.objectOutputType<{
90
91
  type: z.ZodString;
@@ -98,8 +99,8 @@ export declare const layoutEnvelopeSchema: z.ZodObject<{
98
99
  } & {
99
100
  [k: string]: unknown;
100
101
  };
101
- version: 1;
102
102
  }, {
103
+ version: 1;
103
104
  data: {
104
105
  content: z.objectInputType<{
105
106
  type: z.ZodString;
@@ -113,7 +114,6 @@ export declare const layoutEnvelopeSchema: z.ZodObject<{
113
114
  } & {
114
115
  [k: string]: unknown;
115
116
  };
116
- version: 1;
117
117
  }>;
118
118
  /** Wrap a Puck Data document in the versioned storage envelope. */
119
119
  export declare function wrapLayout(data: LayoutData): LayoutEnvelope;
@@ -1 +1 @@
1
- {"version":3,"file":"_content-config.d.ts","sourceRoot":"","sources":["../../../src/ui/api/_content-config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AACvC,OAAO,KAAK,EACX,kBAAkB,EAKlB,MAAM,yBAAyB,CAAA;AAChC,OAAO,KAAK,EAAgB,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAiBzE;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAczF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,GAAG,kBAAkB,CAgBjE"}
1
+ {"version":3,"file":"_content-config.d.ts","sourceRoot":"","sources":["../../../src/ui/api/_content-config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AACvC,OAAO,KAAK,EACX,kBAAkB,EAKlB,MAAM,yBAAyB,CAAA;AAChC,OAAO,KAAK,EAAgB,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAkBzE;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAczF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,GAAG,kBAAkB,CAiBjE"}
@@ -46,6 +46,7 @@ export function toContentConfig(c) {
46
46
  dispatchFaq: hooks.dispatchFaq,
47
47
  dispatchWebhook: hooks.dispatchWebhook,
48
48
  publishReadiness: hooks.publishReadiness,
49
+ podcastSourceProductionEnabled: hooks.podcastSourceProductionEnabled,
49
50
  };
50
51
  }
51
52
  //# sourceMappingURL=_content-config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"_content-config.js","sourceRoot":"","sources":["../../../src/ui/api/_content-config.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,qFAAqF;AAWrF,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAgB5C;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,CAAa,EAAE,MAA+B;IAC1E,MAAM,GAAG,GAAI,CAAC,CAAC,MAA+C,CAAC,MAAM,IAAI,EAAE,CAAA;IAC3E,MAAM,MAAM,GACV,CAAC,CAAC,MAA0C,CAAC,IAAI,EAAE,OAAO;QAC1D,GAAG,CAAC,iBAAwC;QAC7C,IAAI,CAAA;IACL,OAAO;QACN,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,MAAM,EAAE,MAAM,IAAK,CAAC,CAAC,MAAiC;QACtD,EAAE,EAAE,GAAG,CAAC,OAA6B;QACrC,MAAM,EAAE,CAAC,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,KAAK,CAA6B;QACnE,MAAM;QACN,GAAG;KACH,CAAA;AACF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,CAAa;IAC5C,MAAM,MAAM,GAAG,CAAC,CAAC,MAAuC,CAAA;IACxD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAA;IACnC,OAAO;QACN,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC;QACxB,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,KAAK,EAAE;YACN,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,UAAU,EAAE,KAAK,CAAC,UAAU;SAC5B;QACD,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;KACxC,CAAA;AACF,CAAC"}
1
+ {"version":3,"file":"_content-config.js","sourceRoot":"","sources":["../../../src/ui/api/_content-config.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,qFAAqF;AAWrF,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAiB5C;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,CAAa,EAAE,MAA+B;IAC1E,MAAM,GAAG,GAAI,CAAC,CAAC,MAA+C,CAAC,MAAM,IAAI,EAAE,CAAA;IAC3E,MAAM,MAAM,GACV,CAAC,CAAC,MAA0C,CAAC,IAAI,EAAE,OAAO;QAC1D,GAAG,CAAC,iBAAwC;QAC7C,IAAI,CAAA;IACL,OAAO;QACN,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,MAAM,EAAE,MAAM,IAAK,CAAC,CAAC,MAAiC;QACtD,EAAE,EAAE,GAAG,CAAC,OAA6B;QACrC,MAAM,EAAE,CAAC,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,KAAK,CAA6B;QACnE,MAAM;QACN,GAAG;KACH,CAAA;AACF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,CAAa;IAC5C,MAAM,MAAM,GAAG,CAAC,CAAC,MAAuC,CAAA;IACxD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAA;IACnC,OAAO;QACN,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC;QACxB,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,KAAK,EAAE;YACN,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,UAAU,EAAE,KAAK,CAAC,UAAU;SAC5B;QACD,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,8BAA8B,EAAE,KAAK,CAAC,8BAA8B;KACpE,CAAA;AACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"publish-callback.d.ts","sourceRoot":"","sources":["../../../../src/ui/api/fronts/publish-callback.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAUvC,eAAO,MAAM,IAAI,GAAI,GAAG,UAAU,sBACgD,CAAA"}
1
+ {"version":3,"file":"publish-callback.d.ts","sourceRoot":"","sources":["../../../../src/ui/api/fronts/publish-callback.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAIvC,eAAO,MAAM,IAAI,GAAI,GAAG,UAAU,sBACiD,CAAA"}
@@ -1,8 +1,4 @@
1
1
  import { createFrontsPublishRoutes } from '../../../routes/fronts-publish.js';
2
- import { buildSiteAuthz } from '../_authz.js';
3
- function toCtx(c) {
4
- const env = c.locals.cmsEnv ?? {};
5
- return { request: c.request, params: {}, db: env.SITE_DB, env };
6
- }
7
- export const POST = (c) => createFrontsPublishRoutes({ authz: buildSiteAuthz(c) }).publishCallback(toCtx(c));
2
+ import { toContentConfig, toContentCtx } from '../_content-config.js';
3
+ export const POST = (c) => createFrontsPublishRoutes(toContentConfig(c)).publishCallback(toContentCtx(c, {}));
8
4
  //# sourceMappingURL=publish-callback.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"publish-callback.js","sourceRoot":"","sources":["../../../../src/ui/api/fronts/publish-callback.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAA;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAE7C,SAAS,KAAK,CAAC,CAAa;IAC3B,MAAM,GAAG,GAAI,CAAC,CAAC,MAA+C,CAAC,MAAM,IAAI,EAAE,CAAA;IAC3E,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,OAA6B,EAAE,GAAG,EAAE,CAAA;AACtF,CAAC;AAED,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAa,EAAE,EAAE,CACrC,yBAAyB,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"publish-callback.js","sourceRoot":"","sources":["../../../../src/ui/api/fronts/publish-callback.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAA;AAC7E,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAErE,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAa,EAAE,EAAE,CACrC,yBAAyB,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"schedule.d.ts","sourceRoot":"","sources":["../../../../src/ui/api/fronts/schedule.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAUvC,eAAO,MAAM,GAAG,GAAI,GAAG,UAAU,sBAC0C,CAAA"}
1
+ {"version":3,"file":"schedule.d.ts","sourceRoot":"","sources":["../../../../src/ui/api/fronts/schedule.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAIvC,eAAO,MAAM,GAAG,GAAI,GAAG,UAAU,sBAC2C,CAAA"}
@@ -1,8 +1,4 @@
1
1
  import { createFrontsPublishRoutes } from '../../../routes/fronts-publish.js';
2
- import { buildSiteAuthz } from '../_authz.js';
3
- function toCtx(c) {
4
- const env = c.locals.cmsEnv ?? {};
5
- return { request: c.request, params: {}, db: env.SITE_DB, env };
6
- }
7
- export const GET = (c) => createFrontsPublishRoutes({ authz: buildSiteAuthz(c) }).schedule(toCtx(c));
2
+ import { toContentConfig, toContentCtx } from '../_content-config.js';
3
+ export const GET = (c) => createFrontsPublishRoutes(toContentConfig(c)).schedule(toContentCtx(c, {}));
8
4
  //# sourceMappingURL=schedule.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schedule.js","sourceRoot":"","sources":["../../../../src/ui/api/fronts/schedule.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAA;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAE7C,SAAS,KAAK,CAAC,CAAa;IAC3B,MAAM,GAAG,GAAI,CAAC,CAAC,MAA+C,CAAC,MAAM,IAAI,EAAE,CAAA;IAC3E,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,OAA6B,EAAE,GAAG,EAAE,CAAA;AACtF,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,CAAa,EAAE,EAAE,CACpC,yBAAyB,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"schedule.js","sourceRoot":"","sources":["../../../../src/ui/api/fronts/schedule.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAA;AAC7E,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAErE,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,CAAa,EAAE,EAAE,CACpC,yBAAyB,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@growth-labs/cms",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "growth-labs-cms-migrations": "./dist/cli/migrations.js"
@@ -11,6 +11,17 @@
11
11
 
12
12
  import type { FoundryDispatch } from '../providers/types.js'
13
13
  import type { D1Database } from './d1.js'
14
+ import { validPodcastSourceUrl } from './podcast-source.js'
15
+
16
+ interface PodcastDispatchSnapshot {
17
+ content_id: string
18
+ audio_r2_key: string | null
19
+ duration_seconds: number | null
20
+ transcript: string | null
21
+ processing_source_url: string | null
22
+ processing_source_kind: string | null
23
+ processing_trigger_token: string | null
24
+ }
14
25
 
15
26
  export type ProcessingState =
16
27
  | 'pending'
@@ -103,7 +114,9 @@ export async function dispatchToFoundry(
103
114
  hook: Pick<FoundryDispatch, 'dispatchVideo'>,
104
115
  ): Promise<DispatchResult> {
105
116
  const content = await db
106
- .prepare('SELECT id, type, slug, title, description FROM content_items WHERE id = ? LIMIT 1')
117
+ .prepare(
118
+ 'SELECT id, type, slug, title, description, canonical_version, status, deleted_at FROM content_items WHERE id = ? LIMIT 1',
119
+ )
107
120
  .bind(contentId)
108
121
  .first<{
109
122
  id: string
@@ -111,6 +124,9 @@ export async function dispatchToFoundry(
111
124
  slug: string
112
125
  title: string
113
126
  description: string | null
127
+ canonical_version: number
128
+ status: string
129
+ deleted_at: number | null
114
130
  }>()
115
131
  if (!content) {
116
132
  throw new Error(`dispatchToFoundry: content ${contentId} not found`)
@@ -136,6 +152,7 @@ export async function dispatchToFoundry(
136
152
  let sourceKind: string | null = null
137
153
  let videoId: string | null = null
138
154
  let durationSeconds: number | null
155
+ let podcastSnapshot: PodcastDispatchSnapshot | null = null
139
156
 
140
157
  if (videoRow) {
141
158
  table = 'video_content'
@@ -150,14 +167,10 @@ export async function dispatchToFoundry(
150
167
  } else {
151
168
  const podcastRow = await db
152
169
  .prepare(
153
- 'SELECT content_id, audio_r2_key, duration_seconds FROM podcast_content WHERE content_id = ?',
170
+ 'SELECT content_id, audio_r2_key, duration_seconds, transcript, processing_source_url, processing_source_kind, processing_trigger_token FROM podcast_content WHERE content_id = ?',
154
171
  )
155
172
  .bind(contentId)
156
- .first<{
157
- content_id: string
158
- audio_r2_key: string | null
159
- duration_seconds: number | null
160
- }>()
173
+ .first<PodcastDispatchSnapshot>()
161
174
 
162
175
  if (!podcastRow) {
163
176
  throw new Error(
@@ -165,6 +178,10 @@ export async function dispatchToFoundry(
165
178
  )
166
179
  }
167
180
  table = 'podcast_content'
181
+ if (content.type !== 'podcast' || content.deleted_at != null || content.status === 'archived') {
182
+ throw new Error(`dispatchToFoundry: podcast ${contentId} is inactive`)
183
+ }
184
+ podcastSnapshot = podcastRow
168
185
  sourceUrl = podcastRow.audio_r2_key?.trim() ?? ''
169
186
  if (!sourceUrl) {
170
187
  throw new Error(`dispatchToFoundry: content ${contentId} has no podcast audio R2 key`)
@@ -185,7 +202,7 @@ export async function dispatchToFoundry(
185
202
 
186
203
  const triggerToken = crypto.randomUUID()
187
204
  const processingTriggerTokenHash = `sha256:${await sha256Hex(triggerToken)}`
188
- const { correlationId } = await hook.dispatchVideo({
205
+ const { correlationId, podcastSource } = await hook.dispatchVideo({
189
206
  contentId,
190
207
  sourceUrl,
191
208
  kind,
@@ -223,11 +240,49 @@ export async function dispatchToFoundry(
223
240
  .bind(videoId, sourceKind, correlationId, triggerToken, now, contentId)
224
241
  .run()
225
242
  } else {
226
- // podcast_content only has processing_trigger_token (migration 0006)
227
- await db
228
- .prepare('UPDATE podcast_content SET processing_trigger_token = ? WHERE content_id = ?')
229
- .bind(triggerToken, contentId)
243
+ if (!podcastSnapshot) throw new Error('Podcast dispatch snapshot is missing')
244
+ if (
245
+ podcastSource &&
246
+ (podcastSource.kind !== 'https' || !validPodcastSourceUrl(podcastSource.url))
247
+ ) {
248
+ throw new Error('Podcast dispatch returned an invalid source')
249
+ }
250
+ const selectedSource = Boolean(podcastSnapshot.processing_source_url?.trim())
251
+ const capturedUrl = selectedSource
252
+ ? podcastSnapshot.processing_source_url
253
+ : (podcastSource?.url ?? podcastSnapshot.processing_source_url)
254
+ const capturedKind = selectedSource
255
+ ? podcastSnapshot.processing_source_kind
256
+ : (podcastSource?.kind ?? podcastSnapshot.processing_source_kind)
257
+ // A host returns source evidence without mutating editor-owned state.
258
+ // Capture it and the trigger token together only if the exact pre-hook
259
+ // source/audio/editor snapshot is still current. No stale acknowledgement
260
+ // can restore a replaced source or overwrite a newer processing token.
261
+ const written = await db
262
+ .prepare(`UPDATE podcast_content SET processing_source_url = ?, processing_source_kind = ?, processing_trigger_token = ?
263
+ WHERE content_id = ? AND audio_r2_key IS ? AND duration_seconds IS ? AND transcript IS ?
264
+ AND processing_source_url IS ? AND processing_source_kind IS ? AND processing_trigger_token IS ?
265
+ AND EXISTS (SELECT 1 FROM content_items ci WHERE ci.id = podcast_content.content_id
266
+ AND ci.type = 'podcast' AND ci.slug = ? AND ci.canonical_version = ?
267
+ AND ci.status = ? AND ci.deleted_at IS NULL)`)
268
+ .bind(
269
+ capturedUrl,
270
+ capturedKind,
271
+ triggerToken,
272
+ contentId,
273
+ podcastSnapshot.audio_r2_key,
274
+ podcastSnapshot.duration_seconds,
275
+ podcastSnapshot.transcript,
276
+ podcastSnapshot.processing_source_url,
277
+ podcastSnapshot.processing_source_kind,
278
+ podcastSnapshot.processing_trigger_token,
279
+ content.slug,
280
+ content.canonical_version,
281
+ content.status,
282
+ )
230
283
  .run()
284
+ if (written.meta?.changes !== 1)
285
+ throw new Error('Podcast source changed before dispatch acknowledgement')
231
286
  }
232
287
 
233
288
  return { correlationId, triggerToken }
@@ -19,8 +19,9 @@
19
19
  // * ALL times are unix epoch SECONDS — the same column and units the legacy
20
20
  // cron predicate uses, so there is no D1 `datetime('now')` TEXT comparison
21
21
  // on `publish_at` and no space-vs-`T` skew trap.
22
- // * `version` is `content_items.canonical_version`, advanced by exactly one
23
- // seam (updateContentItem) — NOT by a fronts_publish_status marker write and
22
+ // * `version` is `content_items.canonical_version`, advanced by canonical edits
23
+ // and actual delete/restore/archive transitions — NOT by scheduling or a
24
+ // fronts_publish_status marker write and
24
25
  // NOT by polling, so repeated reads are identity-stable while a real
25
26
  // canonical revision does change identity.
26
27
  // * `trigger_token_hash` is `sha256:<hex>` of
@@ -28,7 +29,7 @@
28
29
  // rest exactly like `video_content.processing_trigger_token` (migration
29
30
  // 0006) and NEVER leaves this package.
30
31
  // * Absence is explicit per media type: an article needs a body, a video needs
31
- // a READY HLS manifest, a podcast needs an audio object; `images` and
32
+ // a READY HLS manifest, a podcast needs audio and its transcript; `images` and
32
33
  // `links` are always optionally empty. Anything required and missing is a
33
34
  // `FrontsPublishIntentError`, never an empty default.
34
35
  // * KNOWN GAP, reported not papered over: the wire field is
@@ -39,6 +40,12 @@
39
40
  // other. Real audio HLS is a separate media-pipeline change.
40
41
  import type { ContentType, FrontsPublishStatus } from '../schema/types.js'
41
42
  import type { D1Database } from './d1.js'
43
+ import {
44
+ frontsPodcastSourceGeneration,
45
+ type PodcastPublishBlockerCode,
46
+ podcastPublishBlocker,
47
+ validPodcastSourceUrl,
48
+ } from './podcast-source.js'
42
49
 
43
50
  /** The CMS's own name for the record this intent describes. */
44
51
  export const FRONTS_PUBLISH_INTENT_SOURCE = 'masthead'
@@ -62,6 +69,7 @@ export type FrontsPublishIntentErrorCode =
62
69
  | 'missing_article_body'
63
70
  | 'video_media_not_ready'
64
71
  | 'missing_podcast_audio'
72
+ | PodcastPublishBlockerCode
65
73
 
66
74
  /**
67
75
  * A due item whose canonical data cannot produce a complete intent. Thrown by
@@ -122,12 +130,16 @@ export interface FrontsPublishPodcastAudioRef {
122
130
  * dispatch time" pattern `video_content.processing_source_url` already uses.
123
131
  * `kind` is always `'https'` today: a single literal, not an open enum,
124
132
  * because there is exactly one supported source kind. Present only while the
125
- * podcast has a source but no audio yet; once `podcast_audio_hls` exists this
126
- * is null, so a consumer never sees both at once.
133
+ * podcast is incomplete. An existing audio object with a missing transcript
134
+ * still needs production; it is not a publishable audio reference yet.
127
135
  */
128
136
  export interface FrontsPublishPodcastSourceRef {
129
137
  kind: 'https'
130
138
  url: string
139
+ /** Immutable media identity; rescheduling alone does not change it. */
140
+ generation: string
141
+ /** Positive finite duration measured for the current captured source. */
142
+ duration_seconds: number
131
143
  }
132
144
 
133
145
  export interface FrontsPublishLinkRef {
@@ -141,7 +153,7 @@ export interface FrontsPublishContentRefs {
141
153
  images: FrontsPublishImageRef[]
142
154
  video_hls: FrontsPublishVideoHlsRef | null
143
155
  podcast_audio_hls: FrontsPublishPodcastAudioRef | null
144
- /** Null once podcast_audio_hls exists, or if no source was ever captured. */
156
+ /** Present only while media is incomplete and a valid source was captured. */
145
157
  podcast_source: FrontsPublishPodcastSourceRef | null
146
158
  /** `[]` is valid. */
147
159
  links: FrontsPublishLinkRef[]
@@ -328,6 +340,8 @@ export interface HydrateFrontsPublishIntentsOptions {
328
340
  resolveContentUrl: (row: { type: ContentType; slug: string }) => string
329
341
  /** Public media host used for media rows with no stored URL. Default 'media.fronts.co'. */
330
342
  mediaPublicDomain?: string
343
+ /** False keeps source-only readiness failures visible without requesting production. */
344
+ podcastSourceProductionEnabled?: boolean
331
345
  }
332
346
 
333
347
  /** One due item that could NOT form a complete intent, with the exact reason. */
@@ -357,6 +371,13 @@ export interface HydrateFrontsPublishIntentsResult {
357
371
  incomplete: FrontsPublishIntentFailure[]
358
372
  }
359
373
 
374
+ export class FrontsScheduleOverflowError extends Error {
375
+ constructor(readonly limit: number) {
376
+ super(`Scheduled content exceeds the bounded ${limit}-item response.`)
377
+ this.name = 'FrontsScheduleOverflowError'
378
+ }
379
+ }
380
+
360
381
  /** `?, ?, ?` for an IN clause of `count` bound values. */
361
382
  function placeholders(count: number): string {
362
383
  return new Array(count).fill('?').join(', ')
@@ -379,10 +400,12 @@ interface VideoRefRow {
379
400
 
380
401
  interface PodcastRefRow {
381
402
  content_id: string
403
+ transcript: string | null
382
404
  audio_r2_key: string | null
383
405
  duration_seconds: number | null
384
406
  processing_source_url: string | null
385
407
  processing_source_kind: string | null
408
+ processing_trigger_token: string | null
386
409
  }
387
410
 
388
411
  interface MediaRow {
@@ -430,15 +453,17 @@ export async function hydrateFrontsPublishIntents(
430
453
  `SELECT ${CANDIDATE_COLUMNS}
431
454
  FROM content_items
432
455
  WHERE status = 'scheduled'
456
+ AND deleted_at IS NULL
433
457
  AND publish_at IS NOT NULL
434
458
  AND publish_at <= ?
435
459
  ORDER BY publish_at ASC
436
460
  LIMIT ?`,
437
461
  )
438
- .bind(options.toSeconds, limit)
462
+ .bind(options.toSeconds, limit + 1)
439
463
  .all<IntentCandidateRow>()
440
464
 
441
465
  const rows = candidates.results ?? []
466
+ if (rows.length > limit) throw new FrontsScheduleOverflowError(limit)
442
467
  if (rows.length === 0) return { intents: [], incomplete: [] }
443
468
 
444
469
  const refs = await loadContentRefSources(db, rows)
@@ -452,10 +477,11 @@ export async function hydrateFrontsPublishIntents(
452
477
  mediaPublicDomain,
453
478
  resolve: options.resolveContentUrl,
454
479
  })
455
- intents.push(intent)
456
- // The one dual-signal case (M2-B2): a podcast with a source but no
457
- // audio yet is a real intents[] entry AND an incomplete[] entry at
458
- // once, not either/or -- see buildContentRefs's module comment.
480
+ if (options.podcastSourceProductionEnabled !== false || !intent.content_refs.podcast_source) {
481
+ intents.push(intent)
482
+ }
483
+ // A podcast with a source but incomplete media is visible to the
484
+ // production consumer and remains explicitly unpublishable.
459
485
  if (dualSignal) incomplete.push(dualSignal)
460
486
  } catch (error) {
461
487
  if (error instanceof FrontsPublishIntentError) {
@@ -521,7 +547,7 @@ async function loadContentRefSources(
521
547
  if (podcastIds.length > 0) {
522
548
  const res = await db
523
549
  .prepare(
524
- `SELECT content_id, audio_r2_key, duration_seconds, processing_source_url, processing_source_kind FROM podcast_content
550
+ `SELECT content_id, transcript, audio_r2_key, duration_seconds, processing_source_url, processing_source_kind, processing_trigger_token FROM podcast_content
525
551
  WHERE content_id IN (${placeholders(podcastIds.length)})`,
526
552
  )
527
553
  .bind(...podcastIds)
@@ -596,7 +622,7 @@ async function buildIntent(
596
622
  }
597
623
 
598
624
  const deadline = Math.floor(row.publish_at)
599
- const { refs: contentRefs, incomplete } = buildContentRefs(row, refs, ctx)
625
+ const { refs: contentRefs, incomplete } = await buildContentRefs(row, refs, ctx)
600
626
  return {
601
627
  intent: {
602
628
  intent_id: frontsPublishIntentId(row.id, version),
@@ -621,21 +647,21 @@ async function buildIntent(
621
647
  /**
622
648
  * Per-type content refs, applying the absence rules in the module header.
623
649
  * Returns an `incomplete` note alongside a normally-complete refs object for
624
- * exactly one dual-signal case (M2-B2): a podcast with a processing source
625
- * captured but no audio yet. That item is genuinely not publishable (still
650
+ * a podcast with a processing source and incomplete audio/transcript evidence.
651
+ * That item is not publishable (still
626
652
  * worth an incomplete[] entry, same code as before) but is also not nothing
627
653
  * -- a pull consumer needs `podcast_source` to start production, so this
628
654
  * function returns it rather than throwing it away with the rest of the
629
655
  * intent the way every OTHER missing-required-value case still does.
630
656
  */
631
- function buildContentRefs(
657
+ async function buildContentRefs(
632
658
  row: IntentCandidateRow,
633
659
  refs: Awaited<ReturnType<typeof loadContentRefSources>>,
634
660
  ctx: {
635
661
  mediaPublicDomain: string
636
662
  resolve: (r: { type: ContentType; slug: string }) => string
637
663
  },
638
- ): { refs: FrontsPublishContentRefs; incomplete: FrontsPublishIntentFailure | null } {
664
+ ): Promise<{ refs: FrontsPublishContentRefs; incomplete: FrontsPublishIntentFailure | null }> {
639
665
  const publicUrl = ctx.resolve({ type: row.type, slug: row.slug })
640
666
  const canonicalUrl = row.canonical_url?.trim() || null
641
667
 
@@ -698,26 +724,52 @@ function buildContentRefs(
698
724
  if (row.type === 'podcast') {
699
725
  const found = refs.podcasts.get(row.id)
700
726
  const key = found?.audio_r2_key?.trim()
701
- if (found && key) {
727
+ const blocker = podcastPublishBlocker(found)
728
+ if (found && key && !blocker) {
702
729
  podcastAudio = {
703
730
  kind: 'r2-object',
704
731
  url: objectKeyUrl(key, ctx.mediaPublicDomain),
705
732
  duration_seconds: found.duration_seconds ?? null,
706
733
  }
707
734
  } else {
708
- const message = 'a podcast intent requires a non-empty podcast_content.audio_r2_key'
709
- const sourceUrl = found?.processing_source_url?.trim()
710
- const sourceKind = found?.processing_source_kind?.trim()
711
- if (sourceUrl && sourceKind === 'https') {
712
- podcastSource = { kind: 'https', url: sourceUrl }
735
+ const failure = blocker ?? {
736
+ code: 'missing_podcast_audio' as const,
737
+ message: 'Podcast audio is not ready.',
738
+ }
739
+ const sourceUrl = found?.processing_source_url
740
+ if (found && validPodcastSourceUrl(sourceUrl) && found.processing_source_kind === 'https') {
741
+ if (
742
+ typeof found.duration_seconds !== 'number' ||
743
+ !Number.isFinite(found.duration_seconds) ||
744
+ found.duration_seconds <= 0
745
+ ) {
746
+ throw new FrontsPublishIntentError(
747
+ 'podcast_source_duration_unavailable',
748
+ row.id,
749
+ 'Podcast source duration is not verified.',
750
+ )
751
+ }
752
+ podcastSource = {
753
+ kind: 'https',
754
+ url: sourceUrl,
755
+ duration_seconds: found.duration_seconds,
756
+ generation: await frontsPodcastSourceGeneration({
757
+ content_id: row.id,
758
+ slug: row.slug,
759
+ canonical_version: row.canonical_version as number,
760
+ processing_source_url: found.processing_source_url,
761
+ processing_source_kind: found.processing_source_kind,
762
+ processing_trigger_token: found.processing_trigger_token,
763
+ }),
764
+ }
713
765
  incomplete = {
714
766
  content_id: row.id,
715
- code: 'missing_podcast_audio',
716
- message,
767
+ code: failure.code,
768
+ message: failure.message,
717
769
  publish_at: Math.floor(row.publish_at ?? 0),
718
770
  }
719
771
  } else {
720
- throw new FrontsPublishIntentError('missing_podcast_audio', row.id, message)
772
+ throw new FrontsPublishIntentError(failure.code, row.id, failure.message)
721
773
  }
722
774
  }
723
775
  }