@anthusai/papyrus 0.0.0 → 1.0.0-next.1

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 (254) hide show
  1. package/LICENSE +21 -0
  2. package/amplify/auth/resource.js +61 -0
  3. package/amplify/auth/resource.ts +65 -0
  4. package/amplify/data/resource.js +2037 -0
  5. package/amplify/data/resource.ts +2148 -0
  6. package/amplify/data/schema.js +1850 -0
  7. package/amplify/data/schema.ts +1953 -0
  8. package/amplify/functions/assignment-action/handler.ts +1416 -0
  9. package/amplify/functions/assignment-action/resource.js +8 -0
  10. package/amplify/functions/assignment-action/resource.ts +9 -0
  11. package/amplify/functions/category-action/handler.ts +2259 -0
  12. package/amplify/functions/category-action/resource.js +8 -0
  13. package/amplify/functions/category-action/resource.ts +9 -0
  14. package/amplify/functions/console-chat-responder/Cargo.lock +3456 -0
  15. package/amplify/functions/console-chat-responder/Cargo.toml +26 -0
  16. package/amplify/functions/console-chat-responder/Dockerfile +19 -0
  17. package/amplify/functions/console-chat-responder/py/execute_tactus_runner.py +116 -0
  18. package/amplify/functions/console-chat-responder/resource.js +153 -0
  19. package/amplify/functions/console-chat-responder/resource.ts +179 -0
  20. package/amplify/functions/console-chat-responder/src/main.rs +3242 -0
  21. package/amplify/functions/email-submission-processor/handler.py +46 -0
  22. package/amplify/functions/email-submission-processor/requirements.txt +2 -0
  23. package/amplify/functions/email-submission-processor/resource.js +56 -0
  24. package/amplify/functions/email-submission-processor/resource.ts +69 -0
  25. package/amplify/functions/graphql-jwt-authorizer/handler.ts +193 -0
  26. package/amplify/functions/graphql-jwt-authorizer/resource.js +14 -0
  27. package/amplify/functions/graphql-jwt-authorizer/resource.ts +15 -0
  28. package/amplify/functions/knowledge-query/handler.py +1 -0
  29. package/amplify/functions/knowledge-query/requirements.txt +3 -0
  30. package/amplify/functions/knowledge-query/resource.js +50 -0
  31. package/amplify/functions/knowledge-query/resource.ts +61 -0
  32. package/amplify/functions/manage-user-role/handler.ts +799 -0
  33. package/amplify/functions/manage-user-role/resource.js +8 -0
  34. package/amplify/functions/manage-user-role/resource.ts +9 -0
  35. package/amplify/functions/model-attachment-upload/handler.ts +387 -0
  36. package/amplify/functions/model-attachment-upload/resource.js +8 -0
  37. package/amplify/functions/model-attachment-upload/resource.ts +9 -0
  38. package/amplify/functions/newsroom-summary/handler.ts +321 -0
  39. package/amplify/functions/newsroom-summary/resource.js +8 -0
  40. package/amplify/functions/newsroom-summary/resource.ts +9 -0
  41. package/amplify/functions/procedure-action/handler.ts +621 -0
  42. package/amplify/functions/procedure-action/resource.js +8 -0
  43. package/amplify/functions/procedure-action/resource.ts +9 -0
  44. package/amplify/functions/reader-settings/handler.ts +322 -0
  45. package/amplify/functions/reader-settings/resource.js +8 -0
  46. package/amplify/functions/reader-settings/resource.ts +9 -0
  47. package/amplify/functions/ses-inbound-receive/handler.ts +493 -0
  48. package/amplify/functions/ses-inbound-receive/resource.js +8 -0
  49. package/amplify/functions/ses-inbound-receive/resource.ts +9 -0
  50. package/amplify/functions/shared/amplify-secrets.ts +65 -0
  51. package/amplify/functions/shared/console-chat-enqueue.ts +117 -0
  52. package/amplify/functions/shared/email-mime-intake.ts +196 -0
  53. package/amplify/functions/shared/email-submission.ts +415 -0
  54. package/amplify/functions/shared/inbound-email-intake.ts +37 -0
  55. package/amplify/functions/shared/lambda-data-client.ts +21 -0
  56. package/amplify/functions/shared/model-payloads.ts +186 -0
  57. package/amplify/functions/shared/python-bundle.js +115 -0
  58. package/amplify/functions/shared/python-bundle.ts +141 -0
  59. package/amplify/functions/shared/slack-events.ts +111 -0
  60. package/amplify/functions/slack-delivery/handler.py +112 -0
  61. package/amplify/functions/slack-delivery/requirements.txt +2 -0
  62. package/amplify/functions/slack-delivery/resource.js +38 -0
  63. package/amplify/functions/slack-delivery/resource.ts +51 -0
  64. package/amplify/functions/slack-events/handler.ts +155 -0
  65. package/amplify/functions/slack-events/resource.js +11 -0
  66. package/amplify/functions/slack-events/resource.ts +12 -0
  67. package/amplify/inbound-email/stack.js +46 -0
  68. package/amplify/inbound-email/stack.ts +61 -0
  69. package/amplify/package.json +3 -0
  70. package/amplify/site-backend-config.js +1 -0
  71. package/amplify/site-backend-config.ts +30 -0
  72. package/amplify/site-backend.js +587 -0
  73. package/amplify/site-backend.ts +704 -0
  74. package/amplify/storage/resource.js +18 -0
  75. package/amplify/storage/resource.ts +19 -0
  76. package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +62 -0
  77. package/app/[year]/[month]/[day]/edition-route-page.tsx +53 -0
  78. package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +32 -0
  79. package/app/[year]/[month]/[day]/page.tsx +22 -0
  80. package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +32 -0
  81. package/app/api/archive/editions/route.ts +18 -0
  82. package/app/api/media/[...path]/route.ts +35 -0
  83. package/app/api/revalidate/route.ts +94 -0
  84. package/app/archive/page.tsx +24 -0
  85. package/app/articles/[slug]/page.tsx +35 -0
  86. package/app/globals.css +10329 -0
  87. package/app/layout.tsx +146 -0
  88. package/app/newsroom/[section]/[...selection]/page.tsx +50 -0
  89. package/app/newsroom/[section]/page.tsx +47 -0
  90. package/app/newsroom/forum/[threadId]/page.tsx +35 -0
  91. package/app/newsroom/insights/[threadId]/page.tsx +22 -0
  92. package/app/newsroom/insights/page.tsx +14 -0
  93. package/app/newsroom/layout.tsx +12 -0
  94. package/app/newsroom/page.tsx +31 -0
  95. package/app/newsroom/sections/[sectionId]/page.tsx +17 -0
  96. package/app/page.tsx +272 -0
  97. package/app/settings/page.tsx +14 -0
  98. package/app/tailwind.css +65 -0
  99. package/bin/papyrus-app.mjs +10 -0
  100. package/bin/papyrus-infra.mjs +39 -0
  101. package/components/ai-elements/conversation.tsx +168 -0
  102. package/components/ai-elements/message.tsx +360 -0
  103. package/components/ai-elements/model-selector.tsx +30 -0
  104. package/components/ai-elements/prompt-input.tsx +1461 -0
  105. package/components/ai-elements/shimmer.tsx +76 -0
  106. package/components/ai-elements/suggestion.tsx +54 -0
  107. package/components/ai-elements/tool.tsx +173 -0
  108. package/components/amplify-client-provider.tsx +140 -0
  109. package/components/archive-grid.tsx +89 -0
  110. package/components/archive-shell.tsx +41 -0
  111. package/components/article-video.tsx +47 -0
  112. package/components/blog-page-background.tsx +20 -0
  113. package/components/dev-sandbox-editor-sign-in.tsx +60 -0
  114. package/components/news-desk-client-provider.tsx +211 -0
  115. package/components/news-desk-page.tsx +119 -0
  116. package/components/news-desk-taxonomy-client.ts +3684 -0
  117. package/components/newspaper.tsx +1929 -0
  118. package/components/newsroom-assignments-view.tsx +664 -0
  119. package/components/newsroom-ops-overview.tsx +226 -0
  120. package/components/newsroom-ops-shell.tsx +319 -0
  121. package/components/newsroom-references-view.tsx +598 -0
  122. package/components/newsroom-topics-view.tsx +425 -0
  123. package/components/papyrus-console-shell.tsx +1912 -0
  124. package/components/pictogram-figure.tsx +65 -0
  125. package/components/presentation-footer.tsx +146 -0
  126. package/components/reader-auth-control.tsx +162 -0
  127. package/components/reader-auth-state.ts +122 -0
  128. package/components/reader-settings.ts +332 -0
  129. package/components/reader-theme-control.tsx +85 -0
  130. package/components/reference-source-preview.tsx +163 -0
  131. package/components/settings-page.tsx +239 -0
  132. package/components/topic-steering-workspace.tsx +18063 -0
  133. package/components/ui/badge.tsx +30 -0
  134. package/components/ui/button-group.tsx +87 -0
  135. package/components/ui/button.tsx +58 -0
  136. package/components/ui/card.tsx +32 -0
  137. package/components/ui/command.tsx +194 -0
  138. package/components/ui/dialog.tsx +158 -0
  139. package/components/ui/dropdown-menu.tsx +266 -0
  140. package/components/ui/hover-card.tsx +51 -0
  141. package/components/ui/input-group.tsx +158 -0
  142. package/components/ui/input.tsx +20 -0
  143. package/components/ui/scroll-area.tsx +16 -0
  144. package/components/ui/select.tsx +199 -0
  145. package/components/ui/separator.tsx +23 -0
  146. package/components/ui/sheet.tsx +92 -0
  147. package/components/ui/spinner.tsx +10 -0
  148. package/components/ui/tabs.tsx +113 -0
  149. package/components/ui/textarea.tsx +18 -0
  150. package/components/ui/tooltip.tsx +66 -0
  151. package/components/use-resolved-papyrus-theme.ts +47 -0
  152. package/infra/amplify-app-shell.js +109 -0
  153. package/infra/amplify-app-shell.ts +155 -0
  154. package/infra/index.js +12 -0
  155. package/lib/amplify-outputs-guard.ts +24 -0
  156. package/lib/amplify-outputs-path.ts +16 -0
  157. package/lib/amplify-server-runtime.ts +63 -0
  158. package/lib/archive-data.ts +30 -0
  159. package/lib/archive-types.ts +15 -0
  160. package/lib/articles.ts +320 -0
  161. package/lib/cached-content-repository.ts +89 -0
  162. package/lib/category-dashboard.ts +37 -0
  163. package/lib/category-repository.ts +1907 -0
  164. package/lib/console-chat-client.ts +1316 -0
  165. package/lib/console-web-context.ts +118 -0
  166. package/lib/content-load-timing.ts +23 -0
  167. package/lib/content-repository.ts +40 -0
  168. package/lib/content-types.ts +104 -0
  169. package/lib/define-site.js +3 -0
  170. package/lib/define-site.ts +21 -0
  171. package/lib/demo-amplify-outputs.ts +15 -0
  172. package/lib/dev-sandbox-editor-auth.ts +18 -0
  173. package/lib/doctrine.ts +128 -0
  174. package/lib/edition-routes.ts +155 -0
  175. package/lib/edition-sections.ts +111 -0
  176. package/lib/empty-edition-layout-plan.ts +109 -0
  177. package/lib/excerpts.ts +8 -0
  178. package/lib/graphql-content-repository.ts +986 -0
  179. package/lib/image-url.ts +10 -0
  180. package/lib/layout-plan.ts +981 -0
  181. package/lib/layout-scenarios.ts +564 -0
  182. package/lib/markus-body.ts +168 -0
  183. package/lib/markus-ir.ts +235 -0
  184. package/lib/markus-projection.ts +243 -0
  185. package/lib/markus-to-article.ts +93 -0
  186. package/lib/news-desk-session.ts +160 -0
  187. package/lib/newspaper-layout.ts +3459 -0
  188. package/lib/newsroom-assignments.ts +173 -0
  189. package/lib/newsroom-base-path.ts +48 -0
  190. package/lib/newsroom-card-layout.ts +102 -0
  191. package/lib/newsroom-category-drilldown.ts +133 -0
  192. package/lib/newsroom-demo-dashboard.ts +267 -0
  193. package/lib/newsroom-demo-profile.ts +46 -0
  194. package/lib/newsroom-forum-routes.ts +153 -0
  195. package/lib/newsroom-index-filters.ts +202 -0
  196. package/lib/newsroom-knowledge-query-request.ts +51 -0
  197. package/lib/newsroom-list-selection.ts +42 -0
  198. package/lib/newsroom-nav.ts +42 -0
  199. package/lib/newsroom-references.ts +116 -0
  200. package/lib/newsroom-sections.ts +52 -0
  201. package/lib/newsroom-topics.ts +118 -0
  202. package/lib/papyrus-object-kinds.ts +24 -0
  203. package/lib/papyrus-web-locations.ts +480 -0
  204. package/lib/pilobol-sample.ts +167 -0
  205. package/lib/presentation-footer.ts +92 -0
  206. package/lib/pretext-layout.ts +179 -0
  207. package/lib/public-placeholder-config.ts +102 -0
  208. package/lib/publication-items.ts +181 -0
  209. package/lib/reader-route-config.ts +1 -0
  210. package/lib/reader-static-params.ts +56 -0
  211. package/lib/reader-storage-url.ts +63 -0
  212. package/lib/reference-display.ts +74 -0
  213. package/lib/reference-policy.ts +97 -0
  214. package/lib/reference-source-preview.ts +175 -0
  215. package/lib/renderer-config.ts +50 -0
  216. package/lib/renderer.ts +87 -0
  217. package/lib/reporting-story-budget.ts +638 -0
  218. package/lib/semantic-graph.ts +554 -0
  219. package/lib/site-brand-tenant.ts +41 -0
  220. package/lib/site-brand.ts +179 -0
  221. package/lib/site-renderer.ts +17 -0
  222. package/lib/site-stack.ts +195 -0
  223. package/lib/themed-image.ts +30 -0
  224. package/lib/utils.ts +6 -0
  225. package/middleware.ts +78 -0
  226. package/package.json +296 -1
  227. package/publications/anth_us/brand.ts +68 -0
  228. package/publications/anth_us/theme.css +66 -0
  229. package/publications/papyrus/theme.css +11 -0
  230. package/publications/pilobol_us/brand.ts +37 -0
  231. package/publications/pilobol_us/theme.css +140 -0
  232. package/publications/threat_intelligence/blog-defense/graph.ts +636 -0
  233. package/publications/threat_intelligence/blog-defense/layout.ts +934 -0
  234. package/publications/threat_intelligence/blog-defense/page-background.tsx +590 -0
  235. package/publications/threat_intelligence/brand.ts +40 -0
  236. package/publications/threat_intelligence/pictograms/art.tsx +608 -0
  237. package/publications/threat_intelligence/pictograms/figure.tsx +82 -0
  238. package/publications/threat_intelligence/pictograms/registry.ts +30 -0
  239. package/publications/threat_intelligence/pictograms/system.tsx +305 -0
  240. package/publications/threat_intelligence/seed/seed-edition-content.json +629 -0
  241. package/publications/threat_intelligence/theme.css +1440 -0
  242. package/renderers/markus/index.ts +25 -0
  243. package/renderers/markus/stubs.tsx +33 -0
  244. package/renderers/pretext/article-page.tsx +174 -0
  245. package/renderers/pretext/index.ts +30 -0
  246. package/renderers/pretext/layouts.ts +16 -0
  247. package/renderers/pretext/presentation-shell.tsx +477 -0
  248. package/routes.manifest.json +182 -0
  249. package/scripts/fixtures/pilobol-sample.ast.json +234 -0
  250. package/src/backend.js +1 -0
  251. package/src/backend.ts +3 -0
  252. package/src/sync.mjs +87 -0
  253. package/src/with-papyrus.d.mts +2 -0
  254. package/src/with-papyrus.mjs +47 -0
@@ -0,0 +1,321 @@
1
+ import { getAmplifyDataClientConfig } from "@aws-amplify/backend/function/runtime";
2
+ import { Amplify } from "aws-amplify";
3
+ import { generateClient } from "aws-amplify/data";
4
+ import type { Schema } from "../../data/resource";
5
+ import { putJsonModelPayload, readJsonModelPayload } from "../shared/model-payloads";
6
+
7
+ type DataClient = ReturnType<typeof generateClient<Schema>>;
8
+ type DataClientErrors = Array<{ message?: string | null } | string | null> | null | undefined;
9
+
10
+ const NEWSROOM_SUMMARY_PAYLOAD_ID = "knowledge-raw-payload-newsroom-summary-current";
11
+ const NEWSROOM_SUMMARY_PAYLOAD_OWNER_KIND = "knowledgeRawPayload";
12
+ const SUMMARY_STALE_AFTER_MS = 15 * 60 * 1000;
13
+ let clientPromise: Promise<DataClient> | null = null;
14
+
15
+ export const handler = async (
16
+ event: Parameters<Schema["getNewsroomSummary"]["functionHandler"]>[0] | Parameters<Schema["updateNewsroomSummary"]["functionHandler"]>[0],
17
+ ) => {
18
+ const fieldName = typeof event.info?.fieldName === "string" ? event.info.fieldName : "getNewsroomSummary";
19
+ if (fieldName === "updateNewsroomSummary" || "delta" in event.arguments) {
20
+ return updateNewsroomSummary(event as Parameters<Schema["updateNewsroomSummary"]["functionHandler"]>[0]);
21
+ }
22
+ return getNewsroomSummary();
23
+ };
24
+
25
+ async function getNewsroomSummary() {
26
+ const client = await getDataClient();
27
+ const response = await client.models.KnowledgeRawPayload.get({ id: NEWSROOM_SUMMARY_PAYLOAD_ID });
28
+ assertNoDataErrors(response.errors, "get Newsroom summary snapshot");
29
+ const payload = await readNewsroomSummaryPayload(client);
30
+ return summaryFromPayload(normalizeSummaryPayload(payload, response.data?.updatedAt ?? new Date().toISOString()));
31
+ }
32
+
33
+ async function updateNewsroomSummary(event: Parameters<Schema["updateNewsroomSummary"]["functionHandler"]>[0]) {
34
+ const client = await getDataClient();
35
+ const now = new Date().toISOString();
36
+ const response = await client.models.KnowledgeRawPayload.get({ id: NEWSROOM_SUMMARY_PAYLOAD_ID });
37
+ assertNoDataErrors(response.errors, "get Newsroom summary snapshot");
38
+ const currentPayload = normalizeSummaryPayload(await readNewsroomSummaryPayload(client), now);
39
+ const delta = parseJsonObject(event.arguments.delta);
40
+ const nextPayload = applySummaryDelta(currentPayload, delta, {
41
+ now,
42
+ actorLabel: normalizeOptionalString(event.arguments.actorLabel),
43
+ reason: normalizeOptionalString(event.arguments.reason),
44
+ });
45
+ await upsertNewsroomSummaryPayload(client, nextPayload, response.data, now);
46
+ return summaryFromPayload(nextPayload);
47
+ }
48
+
49
+ async function getDataClient(): Promise<DataClient> {
50
+ if (!clientPromise) {
51
+ clientPromise = (async () => {
52
+ const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig(process.env as never);
53
+ Amplify.configure(resourceConfig, libraryOptions);
54
+ return generateClient<Schema>();
55
+ })();
56
+ }
57
+ return clientPromise;
58
+ }
59
+
60
+ async function upsertNewsroomSummaryPayload(
61
+ client: DataClient,
62
+ payload: ReturnType<typeof normalizeSummaryPayload>,
63
+ current: any,
64
+ now: string,
65
+ ): Promise<void> {
66
+ const input = {
67
+ id: NEWSROOM_SUMMARY_PAYLOAD_ID,
68
+ ownerType: "newsroom",
69
+ ownerId: "newsroom",
70
+ payloadKind: "summary-snapshot",
71
+ importRunId: normalizeOptionalString(payload.latestImportRun?.id),
72
+ createdAt: current?.createdAt ?? now,
73
+ updatedAt: now,
74
+ };
75
+ if (current) {
76
+ await requireDataResult(client.models.KnowledgeRawPayload.update(input), "update Newsroom summary snapshot");
77
+ } else {
78
+ await requireDataResult(client.models.KnowledgeRawPayload.create(input), "create Newsroom summary snapshot");
79
+ }
80
+ await putJsonModelPayload(
81
+ client as any,
82
+ { ownerKind: NEWSROOM_SUMMARY_PAYLOAD_OWNER_KIND, ownerId: NEWSROOM_SUMMARY_PAYLOAD_ID, importRunId: input.importRunId },
83
+ "raw_payload",
84
+ "summary-snapshot",
85
+ payload,
86
+ { filename: "summary-snapshot.json", now },
87
+ );
88
+ }
89
+
90
+ async function readNewsroomSummaryPayload(client: DataClient): Promise<Record<string, unknown> | null> {
91
+ return readJsonModelPayload(
92
+ client as any,
93
+ NEWSROOM_SUMMARY_PAYLOAD_OWNER_KIND,
94
+ NEWSROOM_SUMMARY_PAYLOAD_ID,
95
+ "raw_payload",
96
+ "summary-snapshot",
97
+ );
98
+ }
99
+
100
+ function summaryFromPayload(payload: ReturnType<typeof normalizeSummaryPayload>) {
101
+ return {
102
+ generatedAt: payload.generatedAt,
103
+ staleAt: payload.staleAt,
104
+ source: payload.source,
105
+ latestImportRun: payload.latestImportRun,
106
+ counts: payload.counts,
107
+ facets: payload.facets,
108
+ assignmentStatusCounts: payload.assignmentStatusCounts,
109
+ assignmentTypeCounts: payload.assignmentTypeCounts,
110
+ referenceStatusCounts: payload.referenceStatusCounts,
111
+ messageKindCounts: payload.messageKindCounts,
112
+ messageDomainCounts: payload.messageDomainCounts,
113
+ };
114
+ }
115
+
116
+ function applySummaryDelta(
117
+ payload: ReturnType<typeof normalizeSummaryPayload>,
118
+ delta: Record<string, unknown>,
119
+ options: { now: string; actorLabel: string | null; reason: string | null },
120
+ ) {
121
+ const next = {
122
+ ...payload,
123
+ counts: { ...payload.counts },
124
+ facets: cloneFacets(payload.facets),
125
+ assignmentStatusCounts: { ...payload.assignmentStatusCounts },
126
+ assignmentTypeCounts: { ...payload.assignmentTypeCounts },
127
+ referenceStatusCounts: { ...payload.referenceStatusCounts },
128
+ messageKindCounts: { ...payload.messageKindCounts },
129
+ messageDomainCounts: { ...payload.messageDomainCounts },
130
+ };
131
+ next.generatedAt = options.now;
132
+ next.staleAt = new Date(Date.parse(options.now) + SUMMARY_STALE_AFTER_MS).toISOString();
133
+ next.source = normalizeOptionalString(delta.source) ?? "incremental";
134
+ if (options.actorLabel) next.actorLabel = options.actorLabel;
135
+ if (options.reason) next.reason = options.reason;
136
+ const latestImportRun = parseJsonObject(delta.latestImportRun);
137
+ if (Object.keys(latestImportRun).length) next.latestImportRun = latestImportRun;
138
+ const facetDeltas = parseJsonObject(delta.facetDeltas);
139
+ applyNumberDeltas(next.counts, delta.countDeltas);
140
+ if (!hasFacetDelta(facetDeltas, "assignments", "byStatus")) {
141
+ applyNumberDeltas(next.facets.assignments.byStatus, delta.assignmentStatusDeltas);
142
+ }
143
+ if (!hasFacetDelta(facetDeltas, "assignments", "byType")) {
144
+ applyNumberDeltas(next.facets.assignments.byType, delta.assignmentTypeDeltas);
145
+ }
146
+ if (!hasFacetDelta(facetDeltas, "references", "byCurationStatus")) {
147
+ applyNumberDeltas(next.facets.references.byCurationStatus, delta.referenceStatusDeltas);
148
+ }
149
+ if (!hasFacetDelta(facetDeltas, "messages", "byKind")) {
150
+ applyNumberDeltas(next.facets.messages.byKind, delta.messageKindDeltas);
151
+ }
152
+ if (!hasFacetDelta(facetDeltas, "messages", "byDomain")) {
153
+ applyNumberDeltas(next.facets.messages.byDomain, delta.messageDomainDeltas);
154
+ }
155
+ applyFacetDeltas(next.facets, delta.facetDeltas);
156
+ syncCompatibilityCounts(next);
157
+ return next;
158
+ }
159
+
160
+ function normalizeSummaryPayload(value: unknown, now: string) {
161
+ const parsed = parseJsonObject(value);
162
+ const facets = normalizeFacets(parsed.facets, parsed);
163
+ return {
164
+ generatedAt: normalizeOptionalString(parsed.generatedAt) ?? now,
165
+ staleAt: normalizeOptionalString(parsed.staleAt) ?? now,
166
+ source: normalizeOptionalString(parsed.source) ?? "missing",
167
+ actorLabel: normalizeOptionalString(parsed.actorLabel),
168
+ reason: normalizeOptionalString(parsed.reason),
169
+ latestImportRun: parseJsonObject(parsed.latestImportRun),
170
+ counts: numberRecord(parsed.counts),
171
+ facets,
172
+ assignmentStatusCounts: { ...facets.assignments.byStatus },
173
+ assignmentTypeCounts: { ...facets.assignments.byType },
174
+ referenceStatusCounts: { ...facets.references.byCurationStatus },
175
+ messageKindCounts: { ...facets.messages.byKind },
176
+ messageDomainCounts: { ...facets.messages.byDomain },
177
+ };
178
+ }
179
+
180
+ function createEmptyFacets() {
181
+ return {
182
+ assignments: { byStatus: {}, byType: {}, bySection: {}, statusByType: {}, statusBySection: {}, typeBySection: {} },
183
+ messages: { byKind: {}, byDomain: {}, byStatus: {}, domainByKind: {} },
184
+ modelAttachments: { byOwnerKind: {}, byRole: {}, byMediaType: {}, byStatus: {} },
185
+ references: { byCurationStatus: {}, byCorpus: {}, statusByCorpus: {} },
186
+ semanticNodes: { byNodeKind: {}, byStatus: {}, byCorpus: {}, byCategorySet: {} },
187
+ semanticRelations: { byRelationTypeKey: {}, byRelationDomain: {}, bySubjectKind: {}, byObjectKind: {} },
188
+ imports: { byCorpus: {} },
189
+ };
190
+ }
191
+
192
+ function normalizeFacets(value: unknown, legacy: Record<string, unknown>) {
193
+ const facets = createEmptyFacets();
194
+ const parsed = parseJsonObject(value);
195
+ mergeFacetSection(facets.assignments, parsed.assignments, ["statusByType", "statusBySection", "typeBySection"]);
196
+ mergeFacetSection(facets.messages, parsed.messages, ["domainByKind"]);
197
+ mergeFacetSection(facets.modelAttachments, parsed.modelAttachments);
198
+ mergeFacetSection(facets.references, parsed.references, ["statusByCorpus"]);
199
+ mergeFacetSection(facets.semanticNodes, parsed.semanticNodes);
200
+ mergeFacetSection(facets.semanticRelations, parsed.semanticRelations);
201
+ mergeFacetSection(facets.imports, parsed.imports);
202
+ Object.assign(facets.assignments.byStatus, numberRecord(legacy.assignmentStatusCounts));
203
+ Object.assign(facets.assignments.byType, numberRecord(legacy.assignmentTypeCounts));
204
+ Object.assign(facets.references.byCurationStatus, numberRecord(legacy.referenceStatusCounts));
205
+ Object.assign(facets.messages.byKind, numberRecord(legacy.messageKindCounts));
206
+ Object.assign(facets.messages.byDomain, numberRecord(legacy.messageDomainCounts));
207
+ return facets;
208
+ }
209
+
210
+ function mergeFacetSection(target: Record<string, any>, source: unknown, nestedKeys: string[] = []): void {
211
+ const parsed = parseJsonObject(source);
212
+ for (const key of Object.keys(target)) {
213
+ target[key] = nestedKeys.includes(key) ? nestedNumberRecord(parsed[key]) : numberRecord(parsed[key]);
214
+ }
215
+ }
216
+
217
+ function cloneFacets(value: ReturnType<typeof createEmptyFacets>) {
218
+ return JSON.parse(JSON.stringify(value)) as ReturnType<typeof createEmptyFacets>;
219
+ }
220
+
221
+ function nestedNumberRecord(value: unknown): Record<string, Record<string, number>> {
222
+ const parsed = parseJsonObject(value);
223
+ const result: Record<string, Record<string, number>> = {};
224
+ for (const [key, nested] of Object.entries(parsed)) {
225
+ const record = numberRecord(nested);
226
+ if (Object.keys(record).length) result[key] = record;
227
+ }
228
+ return result;
229
+ }
230
+
231
+ function applyFacetDeltas(target: Record<string, any>, deltas: unknown): void {
232
+ const parsed = parseJsonObject(deltas);
233
+ for (const [sectionKey, sectionDeltas] of Object.entries(parsed)) {
234
+ const section = target[sectionKey];
235
+ if (!section || typeof section !== "object") continue;
236
+ const parsedSection = parseJsonObject(sectionDeltas);
237
+ for (const [facetKey, facetDelta] of Object.entries(parsedSection)) {
238
+ if (!section[facetKey] || typeof section[facetKey] !== "object") continue;
239
+ if (Object.values(parseJsonObject(facetDelta)).some((entry) => entry && typeof entry === "object" && !Array.isArray(entry))) {
240
+ applyNestedNumberDeltas(section[facetKey], facetDelta);
241
+ } else {
242
+ applyNumberDeltas(section[facetKey], facetDelta);
243
+ }
244
+ }
245
+ }
246
+ }
247
+
248
+ function applyNestedNumberDeltas(target: Record<string, Record<string, number>>, deltas: unknown): void {
249
+ const parsed = parseJsonObject(deltas);
250
+ for (const [outerKey, innerDeltas] of Object.entries(parsed)) {
251
+ if (!target[outerKey] || typeof target[outerKey] !== "object") target[outerKey] = {};
252
+ applyNumberDeltas(target[outerKey], innerDeltas);
253
+ if (!Object.keys(target[outerKey]).length) delete target[outerKey];
254
+ }
255
+ }
256
+
257
+ function hasFacetDelta(facetDeltas: Record<string, unknown>, sectionKey: string, facetKey: string): boolean {
258
+ const section = parseJsonObject(facetDeltas[sectionKey]);
259
+ return Boolean(section[facetKey]);
260
+ }
261
+
262
+ function syncCompatibilityCounts(payload: ReturnType<typeof normalizeSummaryPayload>): void {
263
+ payload.assignmentStatusCounts = { ...payload.facets.assignments.byStatus };
264
+ payload.assignmentTypeCounts = { ...payload.facets.assignments.byType };
265
+ payload.referenceStatusCounts = { ...payload.facets.references.byCurationStatus };
266
+ payload.messageKindCounts = { ...payload.facets.messages.byKind };
267
+ payload.messageDomainCounts = { ...payload.facets.messages.byDomain };
268
+ }
269
+
270
+ function applyNumberDeltas(target: Record<string, number>, deltas: unknown): void {
271
+ const parsed = parseJsonObject(deltas);
272
+ for (const [key, delta] of Object.entries(parsed)) {
273
+ const numericDelta = Number(delta);
274
+ if (!Number.isFinite(numericDelta)) continue;
275
+ const next = Math.max(0, (target[key] ?? 0) + numericDelta);
276
+ if (next === 0) delete target[key];
277
+ else target[key] = next;
278
+ }
279
+ }
280
+
281
+ function numberRecord(value: unknown): Record<string, number> {
282
+ const parsed = parseJsonObject(value);
283
+ const result: Record<string, number> = {};
284
+ for (const [key, entry] of Object.entries(parsed)) {
285
+ const numeric = Number(entry);
286
+ if (Number.isFinite(numeric)) result[key] = numeric;
287
+ }
288
+ return result;
289
+ }
290
+
291
+ function parseJsonObject(value: unknown): Record<string, any> {
292
+ if (value && typeof value === "object" && !Array.isArray(value)) return value as Record<string, any>;
293
+ if (typeof value !== "string") return {};
294
+ try {
295
+ const parsed = JSON.parse(value);
296
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed as Record<string, any> : {};
297
+ } catch {
298
+ return {};
299
+ }
300
+ }
301
+
302
+ async function requireDataResult<T>(promise: Promise<{ data?: T | null; errors?: DataClientErrors }>, operation: string): Promise<T> {
303
+ const response = await promise;
304
+ assertNoDataErrors(response.errors, operation);
305
+ if (!response.data) throw new Error(`${operation} returned no data.`);
306
+ return response.data;
307
+ }
308
+
309
+ function assertNoDataErrors(errors: DataClientErrors, operation: string): void {
310
+ if (!errors?.length) return;
311
+ throw new Error(`${operation} failed: ${errors.map(formatDataError).join("; ")}`);
312
+ }
313
+
314
+ function formatDataError(error: { message?: string | null } | string | null): string {
315
+ if (typeof error === "string") return error;
316
+ return error?.message ?? "GraphQL data operation failed.";
317
+ }
318
+
319
+ function normalizeOptionalString(value: unknown): string | null {
320
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
321
+ }
@@ -0,0 +1,8 @@
1
+ import { defineFunction } from "@aws-amplify/backend";
2
+ export const newsroomSummary = defineFunction({
3
+ name: "papyrus-newsroom-summary",
4
+ entry: "./handler.ts",
5
+ timeoutSeconds: 30,
6
+ memoryMB: 512,
7
+ resourceGroupName: "data",
8
+ });
@@ -0,0 +1,9 @@
1
+ import { defineFunction } from "@aws-amplify/backend";
2
+
3
+ export const newsroomSummary = defineFunction({
4
+ name: "papyrus-newsroom-summary",
5
+ entry: "./handler.ts",
6
+ timeoutSeconds: 30,
7
+ memoryMB: 512,
8
+ resourceGroupName: "data",
9
+ });