@anthusai/papyrus 2.0.0-next.11 → 2.0.0-next.110

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 (227) hide show
  1. package/amplify/auth/resource.js +16 -1
  2. package/amplify/auth/resource.ts +19 -1
  3. package/amplify/data/function-handler.js +20 -0
  4. package/amplify/data/function-handler.ts +40 -0
  5. package/amplify/data/resource.js +152 -62
  6. package/amplify/data/resource.ts +158 -62
  7. package/amplify/data/schema.js +2101 -1987
  8. package/amplify/data/schema.ts +197 -70
  9. package/amplify/functions/assignment-action/data-access.js +2 -0
  10. package/amplify/functions/assignment-action/data-access.ts +2 -0
  11. package/amplify/functions/assignment-action/handler.ts +1 -1
  12. package/amplify/functions/automation-view/handler.py +17 -0
  13. package/amplify/functions/automation-view/resource.js +24 -0
  14. package/amplify/functions/automation-view/resource.ts +34 -0
  15. package/amplify/functions/automation-view/schema.js +26 -0
  16. package/amplify/functions/automation-view/schema.ts +28 -0
  17. package/amplify/functions/automation-view/wire.js +56 -0
  18. package/amplify/functions/automation-view/wire.ts +76 -0
  19. package/amplify/functions/content-actions/handler.py +64 -3
  20. package/amplify/functions/content-actions/requirements.txt +1 -0
  21. package/amplify/functions/content-actions/resource.js +1 -1
  22. package/amplify/functions/content-actions/resource.ts +1 -1
  23. package/amplify/functions/editorial-checks/handler.py +2 -2
  24. package/amplify/functions/editorial-checks/requirements.txt +1 -1
  25. package/amplify/functions/editorial-checks/resource.js +3 -3
  26. package/amplify/functions/editorial-checks/resource.ts +3 -3
  27. package/amplify/functions/editorial-checks/schema.js +38 -8
  28. package/amplify/functions/editorial-checks/schema.ts +38 -8
  29. package/amplify/functions/editorial-checks/wire.js +4 -0
  30. package/amplify/functions/editorial-checks/wire.ts +4 -0
  31. package/amplify/functions/monitoring-view/handler.py +11 -0
  32. package/amplify/functions/monitoring-view/resource.js +24 -0
  33. package/amplify/functions/monitoring-view/resource.ts +34 -0
  34. package/amplify/functions/monitoring-view/schema.js +29 -0
  35. package/amplify/functions/monitoring-view/schema.ts +31 -0
  36. package/amplify/functions/monitoring-view/wire.js +64 -0
  37. package/amplify/functions/monitoring-view/wire.ts +81 -0
  38. package/amplify/functions/public-forms/core.js +247 -0
  39. package/amplify/functions/public-forms/core.ts +310 -0
  40. package/amplify/functions/public-forms/handler.ts +41 -0
  41. package/amplify/functions/public-forms/origins.js +35 -0
  42. package/amplify/functions/public-forms/origins.ts +43 -0
  43. package/amplify/functions/public-forms/resource.js +9 -0
  44. package/amplify/functions/public-forms/resource.ts +10 -0
  45. package/amplify/functions/public-forms/wire.js +25 -0
  46. package/amplify/functions/public-forms/wire.ts +32 -0
  47. package/amplify/functions/shared/editorial-context.ts +63 -4
  48. package/amplify/functions/shared/python-bundle.js +20 -14
  49. package/amplify/functions/shared/python-bundle.ts +25 -18
  50. package/amplify/plugins/newsletter/config.js +245 -0
  51. package/amplify/plugins/newsletter/config.ts +305 -0
  52. package/amplify/plugins/newsletter/function/handler.py +6 -0
  53. package/amplify/plugins/newsletter/function/requirements.txt +5 -0
  54. package/amplify/plugins/newsletter/ops/handler.py +12 -0
  55. package/amplify/plugins/newsletter/ops/resource.js +27 -0
  56. package/amplify/plugins/newsletter/ops/resource.ts +37 -0
  57. package/amplify/plugins/newsletter/papyrus.plugin.js +263 -0
  58. package/amplify/plugins/newsletter/papyrus.plugin.ts +311 -0
  59. package/amplify/plugins/newsletter/schema.js +57 -0
  60. package/amplify/plugins/newsletter/schema.ts +62 -0
  61. package/amplify/plugins/newsletter/secure-string-parameter.js +107 -0
  62. package/amplify/plugins/newsletter/secure-string-parameter.ts +120 -0
  63. package/amplify/plugins/newsletter/sending.js +140 -0
  64. package/amplify/plugins/newsletter/sending.ts +186 -0
  65. package/amplify/plugins/reader-steering/config.js +182 -0
  66. package/amplify/plugins/reader-steering/config.ts +290 -0
  67. package/amplify/plugins/reader-steering/function/ai_moderation.py +246 -0
  68. package/amplify/plugins/reader-steering/function/handler.py +1589 -0
  69. package/amplify/plugins/reader-steering/function/moderation-prompt.md +27 -0
  70. package/amplify/plugins/reader-steering/function/moderation.py +316 -0
  71. package/amplify/plugins/reader-steering/function/proposals.py +487 -0
  72. package/amplify/plugins/reader-steering/function/resource.js +27 -0
  73. package/amplify/plugins/reader-steering/function/resource.ts +35 -0
  74. package/amplify/plugins/reader-steering/papyrus.plugin.js +187 -0
  75. package/amplify/plugins/reader-steering/papyrus.plugin.ts +213 -0
  76. package/amplify/plugins/reader-steering/schema.js +252 -0
  77. package/amplify/plugins/reader-steering/schema.ts +254 -0
  78. package/amplify/plugins/registry.js +4 -0
  79. package/amplify/plugins/registry.ts +4 -0
  80. package/amplify/plugins/signup/schema.js +2 -1
  81. package/amplify/plugins/signup/schema.ts +2 -1
  82. package/amplify/plugins/types.ts +5 -0
  83. package/amplify/plugins/wire.js +1 -0
  84. package/amplify/plugins/wire.ts +1 -0
  85. package/amplify/publication/projection-lease.js +30 -0
  86. package/amplify/publication/projection-lease.ts +39 -0
  87. package/amplify/publication/redirect-and-index.js +105 -0
  88. package/amplify/publication/redirect-rules.js +73 -0
  89. package/amplify/publication/redirect-rules.ts +93 -0
  90. package/amplify/publication/s3-projection.js +7 -1
  91. package/amplify/publication/s3-projection.ts +9 -1
  92. package/amplify/site-backend-config.js +19 -0
  93. package/amplify/site-backend-config.ts +45 -1
  94. package/amplify/site-backend-names.js +4 -2
  95. package/amplify/site-backend-names.ts +4 -2
  96. package/amplify/site-backend.js +42 -4
  97. package/amplify/site-backend.ts +45 -4
  98. package/app/.well-known/papyrus-cli.json/route.ts +9 -0
  99. package/app/api/auth/prospectus/sign-in/route.ts +4 -0
  100. package/app/api/newsletter/confirm/route.ts +13 -0
  101. package/app/api/newsletter/preferences/route.ts +13 -0
  102. package/app/api/newsletter/state/route.ts +9 -0
  103. package/app/api/newsletter/subscribe/route.ts +9 -0
  104. package/app/api/newsletter/unsubscribe/route.ts +13 -0
  105. package/app/api/newsroom/chat/[...path]/route.ts +17 -0
  106. package/app/api/newsroom/curation/context/route.ts +9 -0
  107. package/app/api/newsroom/curation/priming/route.ts +9 -0
  108. package/app/api/newsroom/curation/publication/route.ts +9 -0
  109. package/app/api/reader/comment/delete/route.ts +9 -0
  110. package/app/api/reader/comment/edit/route.ts +9 -0
  111. package/app/api/reader/comment/route.ts +9 -0
  112. package/app/api/reader/flag/route.ts +9 -0
  113. package/app/api/reader/handle/route.ts +9 -0
  114. package/app/api/reader/me/route.ts +9 -0
  115. package/app/api/reader/rating/route.ts +9 -0
  116. package/app/api/reader/report/route.ts +9 -0
  117. package/app/api/reader/retired/route.ts +9 -0
  118. package/app/api/reader/submit/route.ts +9 -0
  119. package/app/api/reader/vote/route.ts +9 -0
  120. package/app/layout.tsx +5 -1
  121. package/bin/papyrus-infra.mjs +20 -1
  122. package/components/automation-panel.tsx +49 -0
  123. package/components/automation-run-now.tsx +97 -0
  124. package/components/automation-view.tsx +195 -0
  125. package/components/editorial-checks-admin-panel.tsx +32 -17
  126. package/components/moderation-panel.tsx +248 -0
  127. package/components/monitoring-overview-view.tsx +422 -0
  128. package/components/monitoring-panel.tsx +60 -0
  129. package/components/news-desk-client-provider.tsx +18 -18
  130. package/components/news-desk-taxonomy-client.ts +28 -8
  131. package/components/newsletter-panel.tsx +474 -0
  132. package/components/newsletter-sections.tsx +163 -0
  133. package/components/newsroom-access-gate.tsx +16 -2
  134. package/components/newsroom-article-editor.tsx +9 -7
  135. package/components/newsroom-articles-view.tsx +6 -6
  136. package/components/newsroom-assignment-detail.tsx +6 -6
  137. package/components/newsroom-board-view.tsx +8 -3
  138. package/components/newsroom-curation-bootstrap.tsx +45 -0
  139. package/components/newsroom-ops-overview.tsx +26 -96
  140. package/components/newsroom-ops-shell.tsx +2 -2
  141. package/components/newsroom-organizer.css +33 -0
  142. package/components/newsroom-overview-skeleton.tsx +25 -0
  143. package/components/newsroom-references-view.tsx +192 -49
  144. package/components/notification-center.tsx +281 -0
  145. package/components/papyrus-console-conversation.css +14 -0
  146. package/components/papyrus-console-conversation.tsx +55 -0
  147. package/components/papyrus-console-shell.tsx +62 -420
  148. package/components/papyrus-private-chat-panel.tsx +134 -0
  149. package/components/publication-cyclotron-panel.tsx +15 -10
  150. package/components/reader-auth-state.ts +1 -1
  151. package/components/reference-add-entry.tsx +75 -0
  152. package/components/reference-curation-controls.tsx +31 -22
  153. package/components/reference-cyclotron-priming.tsx +58 -19
  154. package/components/reference-cyclotron-suggestion.tsx +10 -6
  155. package/components/reference-priming.tsx +14 -10
  156. package/components/reference-relevance-review.tsx +3 -3
  157. package/components/reference-section-suggestion.tsx +1 -1
  158. package/components/topic-steering-workspace.tsx +230 -144
  159. package/infra/amplify-app-shell.js +96 -16
  160. package/infra/amplify-app-shell.ts +108 -18
  161. package/infra/cloud-email-config.js +32 -0
  162. package/infra/cloud-email-config.ts +39 -0
  163. package/infra/cloud-email.js +57 -0
  164. package/infra/cloud-email.test.js +69 -0
  165. package/infra/cloud-email.test.ts +66 -0
  166. package/infra/cloud-email.ts +60 -0
  167. package/infra/index.js +1 -0
  168. package/infra/secure-string-parameter-handler.js +41 -0
  169. package/infra/secure-string-parameter-handler.ts +41 -0
  170. package/infra/secure-string-parameter.js +79 -0
  171. package/infra/secure-string-parameter.ts +91 -0
  172. package/infra/site-config.js +92 -6
  173. package/infra/site-config.ts +138 -6
  174. package/lib/automation-client.ts +54 -0
  175. package/lib/automation-fixture.ts +93 -0
  176. package/lib/automation-overview.ts +252 -0
  177. package/lib/category-repository.ts +14 -12
  178. package/lib/cli-login-discovery.ts +78 -0
  179. package/lib/console-web-context.ts +1 -1
  180. package/lib/curation-routes.ts +345 -0
  181. package/lib/cyclotron-suggestions.ts +4 -4
  182. package/lib/editorial-checks-client.ts +18 -0
  183. package/lib/editorial-checks-demo.ts +1 -1
  184. package/lib/editorial-checks.ts +8 -4
  185. package/lib/editorial-preview.ts +5 -5
  186. package/lib/monitoring-client.ts +83 -0
  187. package/lib/monitoring-overview-fixture.ts +51 -0
  188. package/lib/monitoring-overview.ts +191 -0
  189. package/lib/news-desk-session.ts +12 -21
  190. package/lib/newsletter-api.ts +418 -0
  191. package/lib/newsletter-client.ts +46 -0
  192. package/lib/newsletter-fixture.ts +184 -0
  193. package/lib/newsletter-model.ts +202 -0
  194. package/lib/newsroom-demo-dashboard.ts +1 -1
  195. package/lib/newsroom-nav.ts +1 -1
  196. package/lib/newsroom-organizer-nav.ts +2 -1
  197. package/lib/papyrus-chat-admission.ts +65 -0
  198. package/lib/papyrus-hosted-chat.ts +217 -0
  199. package/lib/papyrus-private-chat-client.ts +81 -0
  200. package/lib/prospectus-session.ts +30 -14
  201. package/lib/prospectus-sign-in-routes.ts +106 -31
  202. package/lib/prospectus-site-identity.ts +46 -6
  203. package/lib/reader-api.ts +145 -0
  204. package/lib/reader-moderation.ts +220 -0
  205. package/lib/reader-retirement-route.ts +89 -0
  206. package/lib/reader-subject.md +29 -0
  207. package/lib/reader-subject.ts +53 -0
  208. package/lib/reference-review-details.ts +71 -0
  209. package/lib/reference-review-request.ts +14 -0
  210. package/lib/relevance-decisions.ts +42 -10
  211. package/lib/relevance-status.ts +2 -2
  212. package/lib/review-client.ts +120 -0
  213. package/lib/review-fixture.ts +45 -0
  214. package/lib/review-requests.ts +246 -0
  215. package/lib/semantic-graph.ts +2 -0
  216. package/lib/shared-chat-admission.ts +182 -0
  217. package/lib/site-brand-tenant.ts +11 -6
  218. package/lib/site-brand.ts +6 -3
  219. package/lib/site-origin-value.ts +50 -0
  220. package/lib/site-origin.ts +8 -0
  221. package/lib/use-reference-review-data.ts +44 -0
  222. package/middleware.ts +14 -3
  223. package/package.json +67 -3
  224. package/routes.manifest.json +238 -2
  225. package/src/sync.mjs +11 -2
  226. package/src/with-papyrus.d.mts +1 -0
  227. package/src/with-papyrus.mjs +17 -1
@@ -9,6 +9,8 @@ export const ASSIGNMENT_DATA_FIELDS = [
9
9
  "Query/fields/getProcedureDefinition",
10
10
  "Query/fields/getModelAttachment",
11
11
  "Query/fields/getNewsroomSection",
12
+ "Query/fields/listNewsroomSections",
13
+ "Query/fields/listNewsroomSectionsByTenantAndSortOrder",
12
14
  "Query/fields/getKnowledgeCorpus",
13
15
  "Query/fields/getKnowledgeImportRun",
14
16
  "Query/fields/getReference",
@@ -11,6 +11,8 @@ export const ASSIGNMENT_DATA_FIELDS = [
11
11
  "Query/fields/getProcedureDefinition",
12
12
  "Query/fields/getModelAttachment",
13
13
  "Query/fields/getNewsroomSection",
14
+ "Query/fields/listNewsroomSections",
15
+ "Query/fields/listNewsroomSectionsByTenantAndSortOrder",
14
16
  "Query/fields/getKnowledgeCorpus",
15
17
  "Query/fields/getKnowledgeImportRun",
16
18
  "Query/fields/getReference",
@@ -316,7 +316,7 @@ async function listAssignmentsForObject(event: Parameters<Schema["listAssignment
316
316
  return contexts;
317
317
  }
318
318
 
319
- async function editorialContext(client: DataClient, scope: { publicationKey?: string; sectionId?: string | null; storyLineageId?: string | null; assignmentId?: string | null; maxCharacters?: number | null }) {
319
+ async function editorialContext(client: DataClient, scope: { publicationKey?: string; sectionId?: string | null; storyLineageId?: string | null; assignmentId?: string | null; maxCharacters?: number | null; optionalPublicationDoctrine?: boolean | null }) {
320
320
  if (!editorialEndpoint) throw new Error("Editorial context requires the configured backend endpoint.");
321
321
  return resolveEditorialContext(client as unknown as EditorialClient, {
322
322
  ...scope, endpoint: editorialEndpoint, tenantId: tenantIdFromEnvironment(), readAttachment: readEditorialAttachment,
@@ -0,0 +1,17 @@
1
+ """The automation-view function: `automationOverview` (editors, admins and operator IAM roles, read only) and `runAutomation` (editors and admins)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from papyrus_content.automation_view import handle
8
+
9
+
10
+ def _account(context: Any) -> str | None:
11
+ arn = getattr(context, "invoked_function_arn", None)
12
+ parts = str(arn or "").split(":")
13
+ return parts[4] if len(parts) > 4 and parts[4] else None
14
+
15
+
16
+ def handler(event: dict[str, Any], context: Any = None) -> dict[str, Any]:
17
+ return handle(event, account_id=_account(context))
@@ -0,0 +1,24 @@
1
+ import { defineFunction } from "@aws-amplify/backend";
2
+ import { Duration } from "aws-cdk-lib";
3
+ import { Architecture, Function, Runtime } from "aws-cdk-lib/aws-lambda";
4
+ import { pythonLambdaCode } from "../shared/python-bundle.js";
5
+ /** AppSync gives a resolver 30 seconds; a table read and ten run lookups need a few. */
6
+ export const AUTOMATION_VIEW_TIMEOUT_SECONDS = 25;
7
+ /** stdlib plus the runtime's boto3: the bundle is `papyrus_content` and nothing else. */
8
+ export const automationView = defineFunction((scope) => new Function(scope, "papyrus-automation-view", {
9
+ runtime: Runtime.PYTHON_3_12,
10
+ architecture: Architecture.ARM_64,
11
+ handler: "handler.handler",
12
+ timeout: Duration.seconds(AUTOMATION_VIEW_TIMEOUT_SECONDS),
13
+ memorySize: 256,
14
+ code: pythonLambdaCode({ functionDir: "automation-view", modules: ["papyrus_content"], noDeps: true }, [
15
+ "bash",
16
+ "-c",
17
+ [
18
+ "set -euo pipefail",
19
+ "mkdir -p /asset-output/papyrus_content",
20
+ "cp amplify/functions/automation-view/handler.py /asset-output/handler.py",
21
+ "cp -R src/papyrus_content/. /asset-output/papyrus_content/",
22
+ ].join(" && "),
23
+ ]),
24
+ }), { resourceGroupName: "data" });
@@ -0,0 +1,34 @@
1
+ import { defineFunction } from "@aws-amplify/backend";
2
+ import { Duration } from "aws-cdk-lib";
3
+ import { Architecture, Function, Runtime } from "aws-cdk-lib/aws-lambda";
4
+ import type { Construct } from "constructs";
5
+ import { pythonLambdaCode } from "../shared/python-bundle";
6
+
7
+ /** AppSync gives a resolver 30 seconds; a table read and ten run lookups need a few. */
8
+ export const AUTOMATION_VIEW_TIMEOUT_SECONDS = 25;
9
+
10
+ /** stdlib plus the runtime's boto3: the bundle is `papyrus_content` and nothing else. */
11
+ export const automationView = defineFunction(
12
+ (scope: Construct) =>
13
+ new Function(scope, "papyrus-automation-view", {
14
+ runtime: Runtime.PYTHON_3_12,
15
+ architecture: Architecture.ARM_64,
16
+ handler: "handler.handler",
17
+ timeout: Duration.seconds(AUTOMATION_VIEW_TIMEOUT_SECONDS),
18
+ memorySize: 256,
19
+ code: pythonLambdaCode(
20
+ { functionDir: "automation-view", modules: ["papyrus_content"], noDeps: true },
21
+ [
22
+ "bash",
23
+ "-c",
24
+ [
25
+ "set -euo pipefail",
26
+ "mkdir -p /asset-output/papyrus_content",
27
+ "cp amplify/functions/automation-view/handler.py /asset-output/handler.py",
28
+ "cp -R src/papyrus_content/. /asset-output/papyrus_content/",
29
+ ].join(" && "),
30
+ ],
31
+ ),
32
+ }),
33
+ { resourceGroupName: "data" },
34
+ );
@@ -0,0 +1,26 @@
1
+ import { a } from "@aws-amplify/backend";
2
+ import { automationView } from "./resource.js";
3
+ import { functionHandler } from "../../data/function-handler.js";
4
+ const editors = ["editor", "admin"];
5
+ /**
6
+ * Core to every site's data API. The Automation page reads the automation records, the monitoring
7
+ * trigger rows they link to, and the procedure runs through `automationOverview`, and starts a run
8
+ * through `runAutomation`. Both admit editors and admins. AppSync also lets an IAM principal call the
9
+ * query when its own policy allows the field (every operation in this schema accepts IAM callers for
10
+ * the functions that call it); the function reads only for an IAM principal of its own account and refuses
11
+ * every IAM caller on `runAutomation`.
12
+ */
13
+ export const automationViewSchemaDefinitions = {
14
+ automationOverview: a
15
+ .query()
16
+ .arguments({ automationId: a.string() })
17
+ .returns(a.json())
18
+ .authorization((allow) => [allow.groups(editors)])
19
+ .handler(functionHandler("automationView", automationView)),
20
+ runAutomation: a
21
+ .mutation()
22
+ .arguments({ automationId: a.string().required(), input: a.json() })
23
+ .returns(a.json())
24
+ .authorization((allow) => [allow.groups(editors)])
25
+ .handler(functionHandler("automationView", automationView)),
26
+ };
@@ -0,0 +1,28 @@
1
+ import { a } from "@aws-amplify/backend";
2
+ import { automationView } from "./resource";
3
+ import { functionHandler } from "../../data/function-handler";
4
+
5
+ const editors = ["editor", "admin"];
6
+
7
+ /**
8
+ * Core to every site's data API. The Automation page reads the automation records, the monitoring
9
+ * trigger rows they link to, and the procedure runs through `automationOverview`, and starts a run
10
+ * through `runAutomation`. Both admit editors and admins. AppSync also lets an IAM principal call the
11
+ * query when its own policy allows the field (every operation in this schema accepts IAM callers for
12
+ * the functions that call it); the function reads only for an IAM principal of its own account and refuses
13
+ * every IAM caller on `runAutomation`.
14
+ */
15
+ export const automationViewSchemaDefinitions = {
16
+ automationOverview: a
17
+ .query()
18
+ .arguments({ automationId: a.string() })
19
+ .returns(a.json())
20
+ .authorization((allow) => [allow.groups(editors)])
21
+ .handler(functionHandler("automationView", automationView)),
22
+ runAutomation: a
23
+ .mutation()
24
+ .arguments({ automationId: a.string().required(), input: a.json() })
25
+ .returns(a.json())
26
+ .authorization((allow) => [allow.groups(editors)])
27
+ .handler(functionHandler("automationView", automationView)),
28
+ };
@@ -0,0 +1,56 @@
1
+ import { Stack } from "aws-cdk-lib";
2
+ import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam";
3
+ import { MONITORING_TABLE_PARAMETER, MONITORING_VIEW_TABLE_ACTIONS, monitoringViewParameterArn, monitoringViewTableArns, } from "../monitoring-view/wire.js";
4
+ /** The reads the function makes of its own site's API with its own role. It writes nothing there. */
5
+ export const AUTOMATION_VIEW_FUNCTION_FIELDS = [
6
+ "Query/fields/getAutomation",
7
+ "Query/fields/getNewsroomProcedureDefinition",
8
+ "Query/fields/getProcedureRun",
9
+ "Query/fields/listAutomations",
10
+ "Query/fields/listProcedureRunsByProcedureAndRequestedAt",
11
+ ];
12
+ /** The procedure runner stack's function (infra/procedures/papyrus-procedure-runner.yaml); Run now starts it asynchronously. */
13
+ export const PROCEDURE_RUNNER_FUNCTION_NAME = "papyrus-procedure-runner";
14
+ /** Run now writes one row of its own to the monitoring table, in a monitor's partition (`MONITOR#<id>`), and nothing else. */
15
+ export const AUTOMATION_RUN_NOW_TABLE_ACTIONS = ["dynamodb:PutItem"];
16
+ export const AUTOMATION_RUN_NOW_LEADING_KEYS = ["MONITOR#*"];
17
+ /**
18
+ * Environment and least-privilege IAM for the automation-view function: a few read fields of the site's own API,
19
+ * the same read-only grant on the monitoring table that the Monitoring page has, one PutItem for Run now's row,
20
+ * and permission to start the procedure runner. A site outside the
21
+ * monitoring account finds no parameter and the page lists its records without runs.
22
+ */
23
+ export function wireAutomationView(backend) {
24
+ const lambda = backend.automationView.resources.lambda;
25
+ const stack = Stack.of(lambda);
26
+ lambda.addEnvironment("PAPYRUS_GRAPHQL_ENDPOINT", backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl);
27
+ lambda.addEnvironment("PAPYRUS_MONITORING_TABLE_PARAMETER", MONITORING_TABLE_PARAMETER);
28
+ lambda.addEnvironment("PROCEDURE_RUNNER_FUNCTION", PROCEDURE_RUNNER_FUNCTION_NAME);
29
+ const apiId = backend.data.resources.graphqlApi.apiId;
30
+ lambda.addToRolePolicy(new PolicyStatement({
31
+ effect: Effect.ALLOW,
32
+ actions: ["appsync:GraphQL"],
33
+ resources: AUTOMATION_VIEW_FUNCTION_FIELDS.map((field) => `arn:aws:appsync:${stack.region}:${stack.account}:apis/${apiId}/types/${field}`),
34
+ }));
35
+ lambda.addToRolePolicy(new PolicyStatement({
36
+ effect: Effect.ALLOW,
37
+ actions: ["ssm:GetParameter"],
38
+ resources: [monitoringViewParameterArn(stack.region, stack.account)],
39
+ }));
40
+ lambda.addToRolePolicy(new PolicyStatement({
41
+ effect: Effect.ALLOW,
42
+ actions: MONITORING_VIEW_TABLE_ACTIONS,
43
+ resources: monitoringViewTableArns(stack.region, stack.account),
44
+ }));
45
+ lambda.addToRolePolicy(new PolicyStatement({
46
+ effect: Effect.ALLOW,
47
+ actions: AUTOMATION_RUN_NOW_TABLE_ACTIONS,
48
+ resources: monitoringViewTableArns(stack.region, stack.account).slice(0, 1),
49
+ conditions: { "ForAllValues:StringLike": { "dynamodb:LeadingKeys": AUTOMATION_RUN_NOW_LEADING_KEYS } },
50
+ }));
51
+ lambda.addToRolePolicy(new PolicyStatement({
52
+ effect: Effect.ALLOW,
53
+ actions: ["lambda:InvokeFunction"],
54
+ resources: [`arn:aws:lambda:${stack.region}:${stack.account}:function:${PROCEDURE_RUNNER_FUNCTION_NAME}`],
55
+ }));
56
+ }
@@ -0,0 +1,76 @@
1
+ import { Stack } from "aws-cdk-lib";
2
+ import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam";
3
+ import type { Function as LambdaFunction } from "aws-cdk-lib/aws-lambda";
4
+ import {
5
+ MONITORING_TABLE_PARAMETER,
6
+ MONITORING_VIEW_TABLE_ACTIONS,
7
+ monitoringViewParameterArn,
8
+ monitoringViewTableArns,
9
+ } from "../monitoring-view/wire";
10
+
11
+ /** The reads the function makes of its own site's API with its own role. It writes nothing there. */
12
+ export const AUTOMATION_VIEW_FUNCTION_FIELDS = [
13
+ "Query/fields/getAutomation",
14
+ "Query/fields/getNewsroomProcedureDefinition",
15
+ "Query/fields/getProcedureRun",
16
+ "Query/fields/listAutomations",
17
+ "Query/fields/listProcedureRunsByProcedureAndRequestedAt",
18
+ ];
19
+
20
+ /** The procedure runner stack's function (infra/procedures/papyrus-procedure-runner.yaml); Run now starts it asynchronously. */
21
+ export const PROCEDURE_RUNNER_FUNCTION_NAME = "papyrus-procedure-runner";
22
+
23
+ /** Run now writes one row of its own to the monitoring table, in a monitor's partition (`MONITOR#<id>`), and nothing else. */
24
+ export const AUTOMATION_RUN_NOW_TABLE_ACTIONS = ["dynamodb:PutItem"];
25
+ export const AUTOMATION_RUN_NOW_LEADING_KEYS = ["MONITOR#*"];
26
+
27
+ /**
28
+ * Environment and least-privilege IAM for the automation-view function: a few read fields of the site's own API,
29
+ * the same read-only grant on the monitoring table that the Monitoring page has, one PutItem for Run now's row,
30
+ * and permission to start the procedure runner. A site outside the
31
+ * monitoring account finds no parameter and the page lists its records without runs.
32
+ */
33
+ export function wireAutomationView(backend: any): void {
34
+ const lambda = backend.automationView.resources.lambda as LambdaFunction;
35
+ const stack = Stack.of(lambda);
36
+ lambda.addEnvironment("PAPYRUS_GRAPHQL_ENDPOINT", backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl as string);
37
+ lambda.addEnvironment("PAPYRUS_MONITORING_TABLE_PARAMETER", MONITORING_TABLE_PARAMETER);
38
+ lambda.addEnvironment("PROCEDURE_RUNNER_FUNCTION", PROCEDURE_RUNNER_FUNCTION_NAME);
39
+ const apiId = backend.data.resources.graphqlApi.apiId as string;
40
+ lambda.addToRolePolicy(
41
+ new PolicyStatement({
42
+ effect: Effect.ALLOW,
43
+ actions: ["appsync:GraphQL"],
44
+ resources: AUTOMATION_VIEW_FUNCTION_FIELDS.map((field) => `arn:aws:appsync:${stack.region}:${stack.account}:apis/${apiId}/types/${field}`),
45
+ }),
46
+ );
47
+ lambda.addToRolePolicy(
48
+ new PolicyStatement({
49
+ effect: Effect.ALLOW,
50
+ actions: ["ssm:GetParameter"],
51
+ resources: [monitoringViewParameterArn(stack.region, stack.account)],
52
+ }),
53
+ );
54
+ lambda.addToRolePolicy(
55
+ new PolicyStatement({
56
+ effect: Effect.ALLOW,
57
+ actions: MONITORING_VIEW_TABLE_ACTIONS,
58
+ resources: monitoringViewTableArns(stack.region, stack.account),
59
+ }),
60
+ );
61
+ lambda.addToRolePolicy(
62
+ new PolicyStatement({
63
+ effect: Effect.ALLOW,
64
+ actions: AUTOMATION_RUN_NOW_TABLE_ACTIONS,
65
+ resources: monitoringViewTableArns(stack.region, stack.account).slice(0, 1),
66
+ conditions: { "ForAllValues:StringLike": { "dynamodb:LeadingKeys": AUTOMATION_RUN_NOW_LEADING_KEYS } },
67
+ }),
68
+ );
69
+ lambda.addToRolePolicy(
70
+ new PolicyStatement({
71
+ effect: Effect.ALLOW,
72
+ actions: ["lambda:InvokeFunction"],
73
+ resources: [`arn:aws:lambda:${stack.region}:${stack.account}:function:${PROCEDURE_RUNNER_FUNCTION_NAME}`],
74
+ }),
75
+ );
76
+ }
@@ -8,7 +8,9 @@ from papyrus_content.edge_invalidation import distribution_ids_from_environment,
8
8
  from papyrus_content.graphql_authoring import create_authoring_client
9
9
  from papyrus_content.markus_renderer.derive import derive_body
10
10
  from papyrus_content.projection.deploy_render import deploy_render, drift_metric_line
11
+ from papyrus_content.projection.lease import ProjectionLeaseHeld
11
12
  from papyrus_content.projection.engine import project, project_all, project_item_pages
13
+ from papyrus_content.reader_steering.task import TASK_NAME as READER_SNAPSHOT_TASK, run_reader_snapshot
12
14
  from papyrus_content.projection.runtime import (
13
15
  DEAD_LETTER_QUEUE_ENV,
14
16
  context_from_environment,
@@ -23,7 +25,10 @@ from papyrus_content.rebuild_trigger import (
23
25
  trigger_staging_build,
24
26
  )
25
27
  from papyrus_content.story_reporting import save_story_version
26
- from papyrus_content.reference_intake import IntakeError, intake_references
28
+ from papyrus_content.publication import ensure_publication_from_environment
29
+ from papyrus_content.editorial_checks import GraphQLEditorialStore, ensure_default_policies
30
+ from papyrus_content.reference_intake import IntakeError, intake_email_submission_references, intake_references
31
+ from papyrus_content.publish_alerts import PUBLISHED_TASK_KEY, announce_published
27
32
  from papyrus_content.publishing import (
28
33
  ItemFields,
29
34
  PublishError,
@@ -116,11 +121,17 @@ def projection_context(client):
116
121
  return context
117
122
 
118
123
 
119
- def project_item_action(client, item_id: str, *, unpublished: bool) -> dict:
124
+ def project_item_action(client, item_id: str, *, unpublished: bool, published_item_id: str | None = None) -> dict:
125
+ """Project one item. ``published_item_id`` is passed only for a real publish: the alert is announced once the page
126
+ is live, here when the projection succeeds, or by the scheduled retry (which carries the same marker) when it did not."""
120
127
  if not projection_configured():
128
+ if published_item_id:
129
+ announce_published(item_id, published_item_id)
121
130
  return {}
122
131
  liveness = {} if unpublished else {"live": False}
123
132
  task = {"task": "item", "itemId": item_id, "unpublished": unpublished}
133
+ if published_item_id:
134
+ task[PUBLISHED_TASK_KEY] = {"publishedItemId": published_item_id}
124
135
  try:
125
136
  report = project(projection_context(client), item_id, unpublished=unpublished)
126
137
  except Exception as error: # noqa: BLE001 - a projection failure never fails the save
@@ -142,6 +153,8 @@ def project_item_action(client, item_id: str, *, unpublished: bool) -> dict:
142
153
  schedule_unpurged_invalidation(report.unpurged)
143
154
  if report.follow_up_item_pages:
144
155
  schedule_projection_task({"task": "item-pages", "exceptItemId": item_id}, "follow-up")
156
+ if report.ok and published_item_id:
157
+ announce_published(item_id, published_item_id)
145
158
  return response
146
159
 
147
160
 
@@ -149,8 +162,19 @@ def schedule_unpurged_invalidation(keys: list[str]) -> None:
149
162
  schedule_projection_task({"task": "invalidate", "keys": keys}, "edge invalidation failed")
150
163
 
151
164
 
165
+ def ensure_default_editorial_policies(client) -> None:
166
+ """A deployed site starts with working editorial policies. Never fails the deploy: a problem is logged."""
167
+ try:
168
+ result = ensure_default_policies(GraphQLEditorialStore(execute=client.graphql))
169
+ print(json.dumps({"defaultEditorialPolicies": result}))
170
+ except Exception as error:
171
+ print(json.dumps({"defaultEditorialPoliciesError": str(error)[:500]}))
172
+
173
+
152
174
  def run_deploy_render_task(client) -> dict:
153
175
  """Render every published page after a backend deploy, then report drift (a dry run: nothing is deleted)."""
176
+ ensure_publication_from_environment(client)
177
+ ensure_default_editorial_policies(client)
154
178
  context = projection_context(client)
155
179
  report = deploy_render(context)
156
180
  print(drift_metric_line(report))
@@ -163,6 +187,13 @@ def run_deploy_render_task(client) -> dict:
163
187
 
164
188
  def run_projection_task(task: dict) -> dict:
165
189
  """An asynchronous projection task. Raising makes Lambda retry it, then send the event to the dead letter queue."""
190
+ try:
191
+ return _run_projection_task(task)
192
+ except ProjectionLeaseHeld as held: # another projection of this publication outlasted the wait: retry later
193
+ raise ProjectionTaskFailed(str(held)) from held
194
+
195
+
196
+ def _run_projection_task(task: dict) -> dict:
166
197
  client, _claims = create_authoring_client()
167
198
  if task.get("task") == "item-pages":
168
199
  report = project_item_pages(projection_context(client), except_item_id=task.get("exceptItemId"))
@@ -174,6 +205,8 @@ def run_projection_task(task: dict) -> dict:
174
205
  report = project_all(projection_context(client))
175
206
  elif task.get("task") == "deploy-render":
176
207
  return run_deploy_render_task(client)
208
+ elif task.get("task") == READER_SNAPSHOT_TASK:
209
+ return run_reader_snapshot(projection_context(client), client, poll_retirements=task.get("retirementPoll") is True).to_dict()
177
210
  elif task.get("task") == "invalidate":
178
211
  invalidate = projection_context(client).invalidate
179
212
  if invalidate is None:
@@ -185,6 +218,10 @@ def run_projection_task(task: dict) -> dict:
185
218
  schedule_unpurged_invalidation(report.unpurged)
186
219
  if not report.ok:
187
220
  raise ProjectionTaskFailed("; ".join(report.errors))
221
+ published = task.get(PUBLISHED_TASK_KEY)
222
+ if task.get("task") == "item" and not task.get("unpublished") and isinstance(published, dict):
223
+ # A real publish (the CLI marks it, as does the newsroom's retry): the page is live, so tell the newsletter.
224
+ announce_published(task["itemId"], published.get("publishedItemId"))
188
225
  return {"ok": True, "paths": [*report.written, *report.deleted]}
189
226
 
190
227
 
@@ -270,7 +307,9 @@ def publish_item_action(arguments: dict, actor: str) -> dict:
270
307
  }
271
308
  if result.changed:
272
309
  response.update(site_triggers_for(client, arguments["id"]))
273
- response.update(project_item_action(client, arguments["id"], unpublished=False))
310
+ response.update(
311
+ project_item_action(client, arguments["id"], unpublished=False, published_item_id=result.published_item_id if result.changed else None)
312
+ )
274
313
  return response
275
314
 
276
315
 
@@ -284,6 +323,17 @@ def unpublish_item_action(arguments: dict, actor: str) -> dict:
284
323
  return response
285
324
 
286
325
 
326
+ def move_story_stage_action(arguments: dict, actor: str) -> dict:
327
+ from papyrus_content.story_reporting import move_story_stage
328
+
329
+ client, _claims = create_authoring_client()
330
+ try:
331
+ result = move_story_stage(client, arguments["lineageId"], arguments["stage"], actor=actor, reason=arguments.get("reason"))
332
+ except ValueError as error:
333
+ return {"ok": False, "errors": [{"code": "invalid-stage", "message": str(error), "line": None}]}
334
+ return {"ok": True, **result}
335
+
336
+
287
337
  def request_staging_build_action() -> dict:
288
338
  cms_app_id = (os.environ.get("PAPYRUS_CMS_AMPLIFY_APP_ID") or "").strip()
289
339
  if not cms_app_id:
@@ -306,12 +356,23 @@ def handler(event, context):
306
356
  return intake_references(client, arguments, event.get("identity") or {})
307
357
  except IntakeError as error:
308
358
  return {"ok": False, "errors": [{"code": "intake-rejected", "message": str(error)}]}
359
+ if field == "intakeEmailSubmissionReferences":
360
+ client, _claims = create_authoring_client()
361
+ try:
362
+ from papyrus_content.monitoring_view import default_email_store_factory
363
+ receipt_store = default_email_store_factory(os.environ)()
364
+ return intake_email_submission_references(
365
+ client, arguments, event.get("identity") or {}, receipt_store=receipt_store)
366
+ except IntakeError as error:
367
+ return {"ok": False, "errors": [{"code": "intake-rejected", "message": str(error)}]}
309
368
  if field == "deriveMarkus":
310
369
  return derive_markus_action(arguments)
311
370
  if field == "saveItemDraft":
312
371
  return save_item_draft_action(arguments, actor)
313
372
  if field == "publishItem":
314
373
  return publish_item_action(arguments, actor)
374
+ if field == "moveStoryStage":
375
+ return move_story_stage_action(arguments, actor)
315
376
  if field == "unpublishItem":
316
377
  return unpublish_item_action(arguments, actor)
317
378
  if field == "requestStagingBuild":
@@ -3,3 +3,4 @@ citeproc-py>=0.8.2,<1
3
3
  anthus-markus==0.7.0
4
4
  Pillow>=11.3,<12
5
5
  awscrt>=0.23,<1
6
+ humanus>=0.33,<0.34
@@ -7,7 +7,7 @@ import { fileURLToPath } from "node:url";
7
7
  import { pythonLambdaCode } from "../shared/python-bundle.js";
8
8
  const dirname = path.dirname(fileURLToPath(import.meta.url));
9
9
  const projectRoot = path.resolve(dirname, "../../..");
10
- export const contentActionsRequirements = ["PyYAML>=6.0.2,<7", "citeproc-py>=0.8.2,<1", "anthus-markus==0.7.0", "Pillow>=11.3,<12", "awscrt>=0.23,<1"];
10
+ export const contentActionsRequirements = ["PyYAML>=6.0.2,<7", "citeproc-py>=0.8.2,<1", "anthus-markus==0.7.0", "Pillow>=11.3,<12", "awscrt>=0.23,<1", "humanus>=0.33,<0.34"];
11
11
  function installCheckoutRequirements(outputDir) {
12
12
  const requirementsPath = path.join(projectRoot, "amplify/functions/content-actions/requirements.txt");
13
13
  execSync(`python3 -m pip install -r "${requirementsPath}" -t "${outputDir}" --no-cache-dir --platform manylinux2014_aarch64 --implementation cp --python-version 3.12 --only-binary=:all: --ignore-requires-python`, { cwd: projectRoot, stdio: "inherit" });
@@ -10,7 +10,7 @@ import { pythonLambdaCode, type SiteBundleFile } from "../shared/python-bundle";
10
10
  const dirname = path.dirname(fileURLToPath(import.meta.url));
11
11
  const projectRoot = path.resolve(dirname, "../../..");
12
12
 
13
- export const contentActionsRequirements = ["PyYAML>=6.0.2,<7", "citeproc-py>=0.8.2,<1", "anthus-markus==0.7.0", "Pillow>=11.3,<12", "awscrt>=0.23,<1"];
13
+ export const contentActionsRequirements = ["PyYAML>=6.0.2,<7", "citeproc-py>=0.8.2,<1", "anthus-markus==0.7.0", "Pillow>=11.3,<12", "awscrt>=0.23,<1", "humanus>=0.33,<0.34"];
14
14
 
15
15
  function installCheckoutRequirements(outputDir: string): void {
16
16
  const requirementsPath = path.join(projectRoot, "amplify/functions/content-actions/requirements.txt");
@@ -4,11 +4,11 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Any
6
6
 
7
- from papyrus_content.editorial_checks import GraphQLEditorialStore, Services, handle, load_provider_keys
7
+ from papyrus_content.editorial_checks import GraphQLEditorialStore, Services, event_field_name, handle, load_provider_keys
8
8
 
9
9
  # Amplify passes secrets to Python functions as placeholders; resolve them once per cold start.
10
10
  load_provider_keys()
11
11
 
12
12
 
13
13
  def handler(event: dict[str, Any], _context: Any = None) -> dict[str, Any]:
14
- return handle(event["info"]["fieldName"], event.get("arguments") or {}, Services(store=GraphQLEditorialStore()))
14
+ return handle(event_field_name(event), event.get("arguments") or {}, Services(store=GraphQLEditorialStore()))
@@ -1 +1 @@
1
- limatus[jev]>=0.32,<0.33
1
+ humanus[jev]>=0.33,<0.34
@@ -6,10 +6,10 @@ import path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { pythonLambdaCode } from "../shared/python-bundle.js";
8
8
  const functionDirectory = path.dirname(fileURLToPath(import.meta.url));
9
- /** AppSync gives a resolver 30 seconds; Limatus's classifier deadline (settings, default 20 s) keeps a check inside this. */
9
+ /** AppSync gives a resolver 30 seconds; Humanus's classifier deadline (settings, default 20 s) keeps a check inside this. */
10
10
  export const EDITORIAL_CHECKS_TIMEOUT_SECONDS = 28;
11
- /** Limatus with the Jev classifier lane; papyrus_content.editorial_checks does the work. */
12
- export const editorialChecksRequirements = ["limatus[jev]>=0.32,<0.33"];
11
+ /** Humanus with the Jev classifier lane; papyrus_content.editorial_checks does the work. */
12
+ export const editorialChecksRequirements = ["humanus[jev]>=0.33,<0.34"];
13
13
  function installRequirements(outputDir) {
14
14
  execFileSync("python3", [
15
15
  "-m", "pip", "install", "-r", path.join(functionDirectory, "requirements.txt"), "-t", outputDir, "--no-cache-dir",
@@ -9,11 +9,11 @@ import { pythonLambdaCode } from "../shared/python-bundle";
9
9
 
10
10
  const functionDirectory = path.dirname(fileURLToPath(import.meta.url));
11
11
 
12
- /** AppSync gives a resolver 30 seconds; Limatus's classifier deadline (settings, default 20 s) keeps a check inside this. */
12
+ /** AppSync gives a resolver 30 seconds; Humanus's classifier deadline (settings, default 20 s) keeps a check inside this. */
13
13
  export const EDITORIAL_CHECKS_TIMEOUT_SECONDS = 28;
14
14
 
15
- /** Limatus with the Jev classifier lane; papyrus_content.editorial_checks does the work. */
16
- export const editorialChecksRequirements = ["limatus[jev]>=0.32,<0.33"];
15
+ /** Humanus with the Jev classifier lane; papyrus_content.editorial_checks does the work. */
16
+ export const editorialChecksRequirements = ["humanus[jev]>=0.33,<0.34"];
17
17
 
18
18
  function installRequirements(outputDir: string): void {
19
19
  execFileSync(
@@ -1,10 +1,11 @@
1
1
  import { a } from "@aws-amplify/backend";
2
2
  import { editorialChecks } from "./resource.js";
3
+ import { functionHandler } from "../../data/function-handler.js";
3
4
  const editors = ["editor", "admin"];
4
5
  /**
5
- * Core to every site's data API. Policies, settings and editors' decisions live in Papyrus; Limatus checks
6
+ * Core to every site's data API. Policies, settings and editors' decisions live in Papyrus; Humanus checks
6
7
  * content against them. Editors read policies and record decisions; admins
7
- * manage policies and settings. Only the function (IAM) calls Limatus.
8
+ * manage policies and settings. Only the function (IAM) calls Humanus.
8
9
  */
9
10
  export const editorialChecksSchemaDefinitions = {
10
11
  EditorialPolicy: a
@@ -47,12 +48,41 @@ export const editorialChecksSchemaDefinitions = {
47
48
  .disableOperations(["subscriptions"])
48
49
  .secondaryIndexes((index) => [index("itemId").queryField("listEditorialFeedbackByItem")])
49
50
  .authorization((allow) => [allow.group("editor").to(["create", "read"]), allow.group("admin")]),
51
+ EditorialCheckRun: a
52
+ .model({
53
+ id: a.id().required(),
54
+ itemId: a.string().required(),
55
+ lineageId: a.string().required(),
56
+ versionNumber: a.integer().required(),
57
+ section: a.string(),
58
+ contentHash: a.string(),
59
+ packVersion: a.string(),
60
+ violations: a.integer().required(),
61
+ reviews: a.integer().required(),
62
+ findings: a.json().required(),
63
+ lanes: a.json(),
64
+ checkedAt: a.datetime().required(),
65
+ })
66
+ .disableOperations(["subscriptions"])
67
+ .secondaryIndexes((index) => [index("lineageId").sortKeys(["versionNumber"]).queryField("listEditorialCheckRunsByLineage")])
68
+ .authorization((allow) => [allow.group("editor").to(["read"]), allow.group("admin")]),
69
+ listEditorialStarterPacks: a
70
+ .query()
71
+ .returns(a.json())
72
+ .authorization((allow) => [allow.groups(editors)])
73
+ .handler(functionHandler("editorialChecks", editorialChecks)),
74
+ checkItemVersion: a
75
+ .mutation()
76
+ .arguments({ itemId: a.id().required() })
77
+ .returns(a.json())
78
+ .authorization((allow) => [allow.groups(editors)])
79
+ .handler(functionHandler("editorialChecks", editorialChecks)),
50
80
  checkEditorial: a
51
81
  .query()
52
82
  .arguments({ body: a.string().required(), format: a.string(), section: a.string() })
53
83
  .returns(a.json())
54
84
  .authorization((allow) => [allow.groups(editors)])
55
- .handler(a.handler.function(editorialChecks)),
85
+ .handler(functionHandler("editorialChecks", editorialChecks)),
56
86
  suggestEditorialRewrite: a
57
87
  .query()
58
88
  .arguments({
@@ -65,28 +95,28 @@ export const editorialChecksSchemaDefinitions = {
65
95
  })
66
96
  .returns(a.json())
67
97
  .authorization((allow) => [allow.groups(editors)])
68
- .handler(a.handler.function(editorialChecks)),
98
+ .handler(functionHandler("editorialChecks", editorialChecks)),
69
99
  draftEditorialQuestions: a
70
100
  .query()
71
101
  .arguments({ title: a.string().required(), guidance: a.string().required(), examples: a.json() })
72
102
  .returns(a.json())
73
103
  .authorization((allow) => [allow.group("admin")])
74
- .handler(a.handler.function(editorialChecks)),
104
+ .handler(functionHandler("editorialChecks", editorialChecks)),
75
105
  tryEditorialPolicy: a
76
106
  .query()
77
107
  .arguments({ policy: a.json().required(), text: a.string().required(), format: a.string() })
78
108
  .returns(a.json())
79
109
  .authorization((allow) => [allow.group("admin")])
80
- .handler(a.handler.function(editorialChecks)),
110
+ .handler(functionHandler("editorialChecks", editorialChecks)),
81
111
  importEditorialStarterPack: a
82
112
  .mutation()
83
113
  .arguments({ name: a.string().required() })
84
114
  .returns(a.json())
85
115
  .authorization((allow) => [allow.group("admin")])
86
- .handler(a.handler.function(editorialChecks)),
116
+ .handler(functionHandler("editorialChecks", editorialChecks)),
87
117
  recalibrateEditorialPolicies: a
88
118
  .mutation()
89
119
  .returns(a.json())
90
120
  .authorization((allow) => [allow.group("admin")])
91
- .handler(a.handler.function(editorialChecks)),
121
+ .handler(functionHandler("editorialChecks", editorialChecks)),
92
122
  };