@anthusai/papyrus 2.0.0-next.2 → 2.0.0-next.20

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 (93) hide show
  1. package/amplify/data/resource.js +13 -0
  2. package/amplify/data/resource.ts +15 -0
  3. package/amplify/data/schema.js +18 -0
  4. package/amplify/data/schema.ts +20 -0
  5. package/amplify/functions/assignment-action/data-access.js +88 -0
  6. package/amplify/functions/assignment-action/data-access.ts +87 -0
  7. package/amplify/functions/content-actions/handler.py +22 -0
  8. package/amplify/functions/editorial-checks/handler.py +14 -0
  9. package/amplify/functions/editorial-checks/requirements.txt +1 -0
  10. package/amplify/functions/editorial-checks/resource.js +43 -0
  11. package/amplify/functions/editorial-checks/resource.ts +60 -0
  12. package/amplify/functions/editorial-checks/schema.js +92 -0
  13. package/amplify/functions/editorial-checks/schema.ts +94 -0
  14. package/amplify/functions/editorial-checks/wire.js +49 -0
  15. package/amplify/functions/editorial-checks/wire.ts +57 -0
  16. package/amplify/functions/model-attachment-upload/handler.ts +1 -0
  17. package/amplify/functions/monitoring-view/handler.py +11 -0
  18. package/amplify/functions/monitoring-view/resource.js +24 -0
  19. package/amplify/functions/monitoring-view/resource.ts +34 -0
  20. package/amplify/functions/monitoring-view/schema.js +16 -0
  21. package/amplify/functions/monitoring-view/schema.ts +18 -0
  22. package/amplify/functions/monitoring-view/wire.js +37 -0
  23. package/amplify/functions/monitoring-view/wire.ts +47 -0
  24. package/amplify/functions/shared/python-bundle.js +20 -14
  25. package/amplify/functions/shared/python-bundle.ts +25 -18
  26. package/amplify/publication/redirect-and-index.js +83 -0
  27. package/amplify/publication/redirect-rules.js +61 -0
  28. package/amplify/publication/redirect-rules.ts +79 -0
  29. package/amplify/publication/s3-projection.js +5 -1
  30. package/amplify/publication/s3-projection.ts +7 -1
  31. package/amplify/site-backend.js +18 -0
  32. package/amplify/site-backend.ts +24 -0
  33. package/app/api/newsroom/curation/context/route.ts +9 -0
  34. package/app/api/newsroom/curation/priming/route.ts +9 -0
  35. package/app/api/newsroom/curation/publication/route.ts +9 -0
  36. package/bin/papyrus-infra.mjs +20 -1
  37. package/components/editorial-checks-admin-panel.tsx +347 -0
  38. package/components/editorial-finding-card.tsx +162 -0
  39. package/components/editorial-findings-panel.tsx +91 -0
  40. package/components/editorial-preview-popover.tsx +103 -0
  41. package/components/markus-body-editor.tsx +213 -0
  42. package/components/markus-ir-preview.tsx +147 -6
  43. package/components/monitoring-overview-view.tsx +240 -0
  44. package/components/monitoring-panel.tsx +40 -0
  45. package/components/newsroom-article-editor.tsx +136 -11
  46. package/components/newsroom-board-view.tsx +6 -7
  47. package/components/newsroom-curation-bootstrap.tsx +45 -0
  48. package/components/newsroom-ops-shell.tsx +7 -3
  49. package/components/newsroom-organizer.css +74 -61
  50. package/components/newsroom-references-view.tsx +14 -14
  51. package/components/newsroom-theme-fonts/broadsheet-green.css +10 -0
  52. package/components/newsroom-theme-fonts/broadsheet-green.tsx +6 -0
  53. package/components/newsroom-theme-fonts/newspaper-gothic.css +9 -0
  54. package/components/newsroom-theme-fonts/newspaper-gothic.tsx +6 -0
  55. package/components/newsroom-theme-fonts/night-desk.css +10 -0
  56. package/components/newsroom-theme-fonts/night-desk.tsx +6 -0
  57. package/components/newsroom-theme-fonts/refined-newsprint.css +11 -0
  58. package/components/newsroom-theme-fonts/refined-newsprint.tsx +6 -0
  59. package/components/newsroom-theme.css +39 -0
  60. package/components/newsroom-theme.tsx +33 -0
  61. package/components/publication-cyclotron-panel.tsx +10 -10
  62. package/components/reference-add-entry.tsx +75 -0
  63. package/components/reference-curation-controls.tsx +30 -21
  64. package/components/reference-cyclotron-priming.tsx +4 -4
  65. package/components/reference-cyclotron-suggestion.tsx +1 -1
  66. package/components/reference-priming.tsx +14 -10
  67. package/components/reference-section-suggestion.tsx +1 -1
  68. package/components/topic-steering-workspace.tsx +27 -2
  69. package/components/use-editorial-checks.ts +133 -0
  70. package/lib/category-repository.ts +11 -9
  71. package/lib/curation-routes.ts +342 -0
  72. package/lib/editorial-checks-backend.ts +16 -0
  73. package/lib/editorial-checks-client.ts +194 -0
  74. package/lib/editorial-checks-demo.ts +147 -0
  75. package/lib/editorial-checks-settings.ts +27 -0
  76. package/lib/editorial-checks.ts +171 -0
  77. package/lib/editorial-preview.ts +228 -0
  78. package/lib/monitoring-client.ts +29 -0
  79. package/lib/monitoring-overview-fixture.ts +51 -0
  80. package/lib/monitoring-overview.ts +144 -0
  81. package/lib/newsroom-board.ts +27 -2
  82. package/lib/relevance-status.ts +2 -2
  83. package/lib/site-brand-tenant.ts +11 -6
  84. package/lib/site-brand.ts +23 -3
  85. package/package.json +46 -3
  86. package/routes.manifest.json +34 -1
  87. package/components/newsroom-fonts/playfair-display-700.ttf +0 -0
  88. package/components/newsroom-fonts/playfair-display-900.ttf +0 -0
  89. package/components/newsroom-fonts/playfair-display-OFL.txt +0 -93
  90. package/components/newsroom-fonts/plus-jakarta-sans-400.ttf +0 -0
  91. package/components/newsroom-fonts/plus-jakarta-sans-600.ttf +0 -0
  92. package/components/newsroom-fonts/plus-jakarta-sans-OFL.txt +0 -93
  93. package/components/newsroom-newsprint-board.css +0 -7
@@ -0,0 +1,61 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ /** Mirrors DIRECTORY_HTML_PATTERN and DIRECTORY_SLASH_PATTERN in standard/config.py. */
4
+ const DIRECTORY_HTML_PATTERN = "^/(?!(?:.*/)?index\\.html$)(.+)\\.html$";
5
+ const DIRECTORY_SLASH_PATTERN = "^/((?:[^/]+/)*[^/.]+)$";
6
+ const START_MARKER = "/*PAPYRUS_PATTERN_RULES*/";
7
+ const END_MARKER = "/*END_PAPYRUS_PATTERN_RULES*/";
8
+ /**
9
+ * The pattern rules a site declares in projection.json: its `redirects.patterns`, plus, for
10
+ * `urls.style: "directory"`, `/x.html` to `/x/`. Mirrors `StandardSettings.edge_rules` in Python.
11
+ */
12
+ export function edgeRulesFromSettings(settings) {
13
+ const rules = (settings.redirects?.patterns ?? []).map((rule) => ({
14
+ p: rule.pattern,
15
+ t: rule.to,
16
+ s: rule.status ?? 301,
17
+ x: rule.except ?? [],
18
+ }));
19
+ if (settings.urls?.style === "directory") {
20
+ // /x.html to /x/ (a directory index file such as /x/index.html is served as it is), then /x to /x/.
21
+ rules.push({ p: DIRECTORY_HTML_PATTERN, t: "/$1/", s: 301, x: ["/404.html"] });
22
+ rules.push({ p: DIRECTORY_SLASH_PATTERN, t: "/$1/", s: 301, x: ["/404"] });
23
+ }
24
+ return rules;
25
+ }
26
+ /** A standard-front-end site publishes Markdown twins unless it turns `agents.markdown` off. */
27
+ export function negotiatesMarkdown(settings) {
28
+ return settings.site !== undefined && settings.agents?.markdown !== false;
29
+ }
30
+ export function readEdgeConfig(staticDir, baseDirectory = process.cwd()) {
31
+ if (staticDir === undefined)
32
+ return { rules: [], negotiateMarkdown: false };
33
+ const settingsPath = resolve(baseDirectory, staticDir, "projection.json");
34
+ if (!existsSync(settingsPath))
35
+ return { rules: [], negotiateMarkdown: false };
36
+ const settings = JSON.parse(readFileSync(settingsPath, "utf8"));
37
+ return { rules: edgeRulesFromSettings(settings), negotiateMarkdown: negotiatesMarkdown(settings) };
38
+ }
39
+ export function readEdgeRules(staticDir, baseDirectory = process.cwd()) {
40
+ return readEdgeConfig(staticDir, baseDirectory).rules;
41
+ }
42
+ const NEGOTIATE_START = "/*PAPYRUS_NEGOTIATE_MARKDOWN*/";
43
+ const NEGOTIATE_END = "/*END_PAPYRUS_NEGOTIATE_MARKDOWN*/";
44
+ /** The CloudFront Function source with the site's pattern rules and Markdown negotiation flag spliced in. */
45
+ export function compileRedirectFunctionSource(source, rules, options = {}) {
46
+ const start = source.indexOf(START_MARKER);
47
+ const end = source.indexOf(END_MARKER);
48
+ if (start === -1 || end === -1 || end < start) {
49
+ throw new Error("redirect function source has no pattern-rule markers");
50
+ }
51
+ let compiled = rules.length === 0 ? source : source.slice(0, start + START_MARKER.length) + JSON.stringify(rules) + source.slice(end);
52
+ if (options.negotiateMarkdown === true) {
53
+ const from = compiled.indexOf(NEGOTIATE_START);
54
+ const to = compiled.indexOf(NEGOTIATE_END);
55
+ if (from === -1 || to === -1 || to < from) {
56
+ throw new Error("redirect function source has no markdown negotiation markers");
57
+ }
58
+ compiled = compiled.slice(0, from + NEGOTIATE_START.length) + "true" + compiled.slice(to);
59
+ }
60
+ return compiled;
61
+ }
@@ -0,0 +1,79 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+
4
+ /** One pattern redirect as the edge function reads it: regex, target with $1..$9, status, excluded paths. */
5
+ export type EdgeRedirectRule = { p: string; t: string; s: number; x: string[] };
6
+
7
+ /** Mirrors DIRECTORY_HTML_PATTERN and DIRECTORY_SLASH_PATTERN in standard/config.py. */
8
+ const DIRECTORY_HTML_PATTERN = "^/(?!(?:.*/)?index\\.html$)(.+)\\.html$";
9
+ const DIRECTORY_SLASH_PATTERN = "^/((?:[^/]+/)*[^/.]+)$";
10
+
11
+ const START_MARKER = "/*PAPYRUS_PATTERN_RULES*/";
12
+ const END_MARKER = "/*END_PAPYRUS_PATTERN_RULES*/";
13
+
14
+ type ProjectionRedirects = { patterns?: { pattern: string; to: string; status?: number; except?: string[] }[] };
15
+ type ProjectionSettings = { site?: unknown; urls?: { style?: string }; redirects?: ProjectionRedirects; agents?: { markdown?: boolean } };
16
+
17
+ /**
18
+ * The pattern rules a site declares in projection.json: its `redirects.patterns`, plus, for
19
+ * `urls.style: "directory"`, `/x.html` to `/x/`. Mirrors `StandardSettings.edge_rules` in Python.
20
+ */
21
+ export function edgeRulesFromSettings(settings: ProjectionSettings): EdgeRedirectRule[] {
22
+ const rules = (settings.redirects?.patterns ?? []).map((rule) => ({
23
+ p: rule.pattern,
24
+ t: rule.to,
25
+ s: rule.status ?? 301,
26
+ x: rule.except ?? [],
27
+ }));
28
+ if (settings.urls?.style === "directory") {
29
+ // /x.html to /x/ (a directory index file such as /x/index.html is served as it is), then /x to /x/.
30
+ rules.push({ p: DIRECTORY_HTML_PATTERN, t: "/$1/", s: 301, x: ["/404.html"] });
31
+ rules.push({ p: DIRECTORY_SLASH_PATTERN, t: "/$1/", s: 301, x: ["/404"] });
32
+ }
33
+ return rules;
34
+ }
35
+
36
+ /** A standard-front-end site publishes Markdown twins unless it turns `agents.markdown` off. */
37
+ export function negotiatesMarkdown(settings: ProjectionSettings): boolean {
38
+ return settings.site !== undefined && settings.agents?.markdown !== false;
39
+ }
40
+
41
+ export type EdgeConfig = { rules: EdgeRedirectRule[]; negotiateMarkdown: boolean };
42
+
43
+ export function readEdgeConfig(staticDir: string | undefined, baseDirectory: string = process.cwd()): EdgeConfig {
44
+ if (staticDir === undefined) return { rules: [], negotiateMarkdown: false };
45
+ const settingsPath = resolve(baseDirectory, staticDir, "projection.json");
46
+ if (!existsSync(settingsPath)) return { rules: [], negotiateMarkdown: false };
47
+ const settings = JSON.parse(readFileSync(settingsPath, "utf8")) as ProjectionSettings;
48
+ return { rules: edgeRulesFromSettings(settings), negotiateMarkdown: negotiatesMarkdown(settings) };
49
+ }
50
+
51
+ export function readEdgeRules(staticDir: string | undefined, baseDirectory: string = process.cwd()): EdgeRedirectRule[] {
52
+ return readEdgeConfig(staticDir, baseDirectory).rules;
53
+ }
54
+
55
+ const NEGOTIATE_START = "/*PAPYRUS_NEGOTIATE_MARKDOWN*/";
56
+ const NEGOTIATE_END = "/*END_PAPYRUS_NEGOTIATE_MARKDOWN*/";
57
+
58
+ /** The CloudFront Function source with the site's pattern rules and Markdown negotiation flag spliced in. */
59
+ export function compileRedirectFunctionSource(
60
+ source: string,
61
+ rules: EdgeRedirectRule[],
62
+ options: { negotiateMarkdown?: boolean } = {},
63
+ ): string {
64
+ const start = source.indexOf(START_MARKER);
65
+ const end = source.indexOf(END_MARKER);
66
+ if (start === -1 || end === -1 || end < start) {
67
+ throw new Error("redirect function source has no pattern-rule markers");
68
+ }
69
+ let compiled = rules.length === 0 ? source : source.slice(0, start + START_MARKER.length) + JSON.stringify(rules) + source.slice(end);
70
+ if (options.negotiateMarkdown === true) {
71
+ const from = compiled.indexOf(NEGOTIATE_START);
72
+ const to = compiled.indexOf(NEGOTIATE_END);
73
+ if (from === -1 || to === -1 || to < from) {
74
+ throw new Error("redirect function source has no markdown negotiation markers");
75
+ }
76
+ compiled = compiled.slice(0, from + NEGOTIATE_START.length) + "true" + compiled.slice(to);
77
+ }
78
+ return compiled;
79
+ }
@@ -7,11 +7,13 @@ import { SqsDestination } from "aws-cdk-lib/aws-lambda-destinations";
7
7
  import * as route53 from "aws-cdk-lib/aws-route53";
8
8
  import * as s3 from "aws-cdk-lib/aws-s3";
9
9
  import * as sqs from "aws-cdk-lib/aws-sqs";
10
+ import { readFileSync } from "node:fs";
10
11
  import { dirname, resolve } from "node:path";
11
12
  import { fileURLToPath } from "node:url";
12
13
  import { assertValidPublicationConfig } from "../site-backend-config.js";
13
14
  import { addRenditionsFunction } from "./renditions-function.js";
14
15
  import { RENDITIONS_FUNCTION_NAME_ENV } from "./renditions-settings.js";
16
+ import { compileRedirectFunctionSource, readEdgeConfig } from "./redirect-rules.js";
15
17
  import { addInvalidationPathAlarm } from "./invalidation-alarm.js";
16
18
  import { distributionIdFromArn, s3ProjectionEnvironment, s3ProjectionPolicyStatements } from "./s3-projection-settings.js";
17
19
  const redirectFunctionSourcePath = resolve(dirname(fileURLToPath(import.meta.url)), "redirect-and-index.js");
@@ -70,7 +72,9 @@ export function addS3ProjectionPublication(scope, props, media) {
70
72
  const keyValueStore = new cloudfront.KeyValueStore(scope, "RedirectsKeyValueStore");
71
73
  const redirectFunction = new cloudfront.Function(scope, "RedirectAndIndexFunction", {
72
74
  runtime: cloudfront.FunctionRuntime.JS_2_0,
73
- code: cloudfront.FunctionCode.fromFile({ filePath: redirectFunctionSourcePath }),
75
+ code: cloudfront.FunctionCode.fromInline(compileRedirectFunctionSource(readFileSync(redirectFunctionSourcePath, "utf8"), readEdgeConfig(props.staticDir).rules, {
76
+ negotiateMarkdown: readEdgeConfig(props.staticDir).negotiateMarkdown,
77
+ })),
74
78
  keyValueStore,
75
79
  });
76
80
  const deadLetterQueue = new sqs.Queue(scope, "ProjectionDeadLetterQueue", {
@@ -10,11 +10,13 @@ import * as route53 from "aws-cdk-lib/aws-route53";
10
10
  import * as s3 from "aws-cdk-lib/aws-s3";
11
11
  import * as sqs from "aws-cdk-lib/aws-sqs";
12
12
  import type { Construct } from "constructs";
13
+ import { readFileSync } from "node:fs";
13
14
  import { dirname, resolve } from "node:path";
14
15
  import { fileURLToPath } from "node:url";
15
16
  import { assertValidPublicationConfig, type PapyrusPublicationConfig } from "../site-backend-config";
16
17
  import { addRenditionsFunction, type RenditionsFunction } from "./renditions-function";
17
18
  import { RENDITIONS_FUNCTION_NAME_ENV, type RenditionsPlan } from "./renditions-settings";
19
+ import { compileRedirectFunctionSource, readEdgeConfig } from "./redirect-rules";
18
20
  import { addInvalidationPathAlarm } from "./invalidation-alarm";
19
21
  import { distributionIdFromArn, s3ProjectionEnvironment, s3ProjectionPolicyStatements } from "./s3-projection-settings";
20
22
 
@@ -112,7 +114,11 @@ export function addS3ProjectionPublication(
112
114
  const keyValueStore = new cloudfront.KeyValueStore(scope, "RedirectsKeyValueStore");
113
115
  const redirectFunction = new cloudfront.Function(scope, "RedirectAndIndexFunction", {
114
116
  runtime: cloudfront.FunctionRuntime.JS_2_0,
115
- code: cloudfront.FunctionCode.fromFile({ filePath: redirectFunctionSourcePath }),
117
+ code: cloudfront.FunctionCode.fromInline(
118
+ compileRedirectFunctionSource(readFileSync(redirectFunctionSourcePath, "utf8"), readEdgeConfig(props.staticDir).rules, {
119
+ negotiateMarkdown: readEdgeConfig(props.staticDir).negotiateMarkdown,
120
+ }),
121
+ ),
116
122
  keyValueStore,
117
123
  });
118
124
  const deadLetterQueue = new sqs.Queue(scope, "ProjectionDeadLetterQueue", {
@@ -20,9 +20,14 @@ import { addInvalidationPathAlarm } from "./publication/invalidation-alarm.js";
20
20
  import { applyCognitoDomainPrefix, authConfigFromEnv, defineSiteAuth, siteAuthRedirectUrls } from "./auth/resource.js";
21
21
  import { PROSPECTUS_SIGN_IN_CALLBACK_PATH, addProspectusGoogleOnlyClient } from "./auth/prospectus-google-client.js";
22
22
  import { defineSiteData } from "./data/resource.js";
23
+ import { editorialChecks } from "./functions/editorial-checks/resource.js";
24
+ import { monitoringView } from "./functions/monitoring-view/resource.js";
25
+ import { wireMonitoringView } from "./functions/monitoring-view/wire.js";
26
+ import { wireEditorialChecks } from "./functions/editorial-checks/wire.js";
23
27
  import { resolvePlugins } from "./plugins/registry.js";
24
28
  import { wirePlugins } from "./plugins/wire.js";
25
29
  import { assignmentAction } from "./functions/assignment-action/resource.js";
30
+ import { scopeAssignmentDataAccess } from "./functions/assignment-action/data-access.js";
26
31
  import { categoryAction } from "./functions/category-action/resource.js";
27
32
  import { ConsoleChatResponderStack } from "./functions/console-chat-responder/resource.js";
28
33
  import { contentActions, setSiteFilesForContentActionsBundle } from "./functions/content-actions/resource.js";
@@ -88,9 +93,11 @@ export function defineSiteBackend(papyrusSite) {
88
93
  auth: defineSiteAuth(authConfig),
89
94
  categoryAction,
90
95
  contentActions,
96
+ editorialChecks,
91
97
  data: defineSiteData(pluginDefinitions),
92
98
  knowledgeQuery,
93
99
  manageUserRole,
100
+ monitoringView,
94
101
  modelAttachmentUpload,
95
102
  newsroomSummary,
96
103
  procedureAction,
@@ -304,10 +311,19 @@ export function defineSiteBackend(papyrusSite) {
304
311
  }
305
312
  grantCorporaRead(backend.knowledgeQuery.resources.lambda);
306
313
  grantNewsroomReadWrite(backend.assignmentAction.resources.lambda);
314
+ scopeAssignmentDataAccess(backend.assignmentAction.resources.lambda, backend.data.resources.graphqlApi.arn);
307
315
  grantNewsroomReadWrite(backend.categoryAction.resources.lambda);
308
316
  grantNewsroomReadWrite(backend.newsroomSummary.resources.lambda);
309
317
  const contentActionsLambda = backend.contentActions.resources.lambda;
310
318
  grantNewsroomReadWriteDelete(contentActionsLambda);
319
+ if ((activeBrand?.corpusKey ?? "").trim() !== "") {
320
+ // A deploy creates the publication's canonical corpus from the brand when the backend has none.
321
+ contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_CORPUS_KEY", (activeBrand?.corpusKey ?? "").trim());
322
+ contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_NAME", (activeBrand?.publicationName ?? activeBrand?.corpusKey ?? "").trim());
323
+ }
324
+ // AppSync validates the token; pending intake additionally binds its actor
325
+ // and publication to this deployment's pool and existing API endpoint.
326
+ contentActionsLambda.addEnvironment("PAPYRUS_USER_POOL_ISSUER", `https://cognito-idp.${Stack.of(contentActionsLambda).region}.amazonaws.com/${backend.auth.resources.userPool.userPoolId}`);
311
327
  contentActionsLambda.addEnvironment("PAPYRUS_GRAPHQL_ENDPOINT", backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl);
312
328
  contentActionsLambda.addToRolePolicy(new PolicyStatement({
313
329
  actions: ["appsync:GraphQL"],
@@ -584,6 +600,8 @@ export function defineSiteBackend(papyrusSite) {
584
600
  : null,
585
601
  },
586
602
  });
603
+ wireEditorialChecks(backend);
604
+ wireMonitoringView(backend);
587
605
  wirePlugins(backend, plugins, contentActionsLambda);
588
606
  return backend;
589
607
  }
@@ -27,9 +27,14 @@ import { addInvalidationPathAlarm } from "./publication/invalidation-alarm";
27
27
  import { applyCognitoDomainPrefix, authConfigFromEnv, defineSiteAuth, siteAuthRedirectUrls } from "./auth/resource";
28
28
  import { PROSPECTUS_SIGN_IN_CALLBACK_PATH, addProspectusGoogleOnlyClient } from "./auth/prospectus-google-client";
29
29
  import { defineSiteData } from "./data/resource";
30
+ import { editorialChecks } from "./functions/editorial-checks/resource";
31
+ import { monitoringView } from "./functions/monitoring-view/resource";
32
+ import { wireMonitoringView } from "./functions/monitoring-view/wire";
33
+ import { wireEditorialChecks } from "./functions/editorial-checks/wire";
30
34
  import { resolvePlugins } from "./plugins/registry";
31
35
  import { wirePlugins } from "./plugins/wire";
32
36
  import { assignmentAction } from "./functions/assignment-action/resource";
37
+ import { scopeAssignmentDataAccess } from "./functions/assignment-action/data-access";
33
38
  import { categoryAction } from "./functions/category-action/resource";
34
39
  import { ConsoleChatResponderStack } from "./functions/console-chat-responder/resource";
35
40
  import { contentActions, setSiteFilesForContentActionsBundle } from "./functions/content-actions/resource";
@@ -109,9 +114,11 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
109
114
  auth: defineSiteAuth(authConfig),
110
115
  categoryAction,
111
116
  contentActions,
117
+ editorialChecks,
112
118
  data: defineSiteData(pluginDefinitions),
113
119
  knowledgeQuery,
114
120
  manageUserRole,
121
+ monitoringView,
115
122
  modelAttachmentUpload,
116
123
  newsroomSummary,
117
124
  procedureAction,
@@ -387,10 +394,25 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
387
394
  }
388
395
  grantCorporaRead(backend.knowledgeQuery.resources.lambda as LambdaFunction);
389
396
  grantNewsroomReadWrite(backend.assignmentAction.resources.lambda as LambdaFunction);
397
+ scopeAssignmentDataAccess(
398
+ backend.assignmentAction.resources.lambda as LambdaFunction,
399
+ backend.data.resources.graphqlApi.arn,
400
+ );
390
401
  grantNewsroomReadWrite(backend.categoryAction.resources.lambda as LambdaFunction);
391
402
  grantNewsroomReadWrite(backend.newsroomSummary.resources.lambda as LambdaFunction);
392
403
  const contentActionsLambda = backend.contentActions.resources.lambda as LambdaFunction;
393
404
  grantNewsroomReadWriteDelete(contentActionsLambda);
405
+ if ((activeBrand?.corpusKey ?? "").trim() !== "") {
406
+ // A deploy creates the publication's canonical corpus from the brand when the backend has none.
407
+ contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_CORPUS_KEY", (activeBrand?.corpusKey ?? "").trim());
408
+ contentActionsLambda.addEnvironment("PAPYRUS_PUBLICATION_NAME", (activeBrand?.publicationName ?? activeBrand?.corpusKey ?? "").trim());
409
+ }
410
+ // AppSync validates the token; pending intake additionally binds its actor
411
+ // and publication to this deployment's pool and existing API endpoint.
412
+ contentActionsLambda.addEnvironment(
413
+ "PAPYRUS_USER_POOL_ISSUER",
414
+ `https://cognito-idp.${Stack.of(contentActionsLambda).region}.amazonaws.com/${backend.auth.resources.userPool.userPoolId}`,
415
+ );
394
416
  contentActionsLambda.addEnvironment(
395
417
  "PAPYRUS_GRAPHQL_ENDPOINT",
396
418
  backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl,
@@ -721,6 +743,8 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
721
743
  : null,
722
744
  },
723
745
  });
746
+ wireEditorialChecks(backend);
747
+ wireMonitoringView(backend);
724
748
  wirePlugins(backend, plugins, contentActionsLambda);
725
749
 
726
750
  return backend;
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleCurationRoute } from "../../../../../lib/curation-routes";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function GET(request: NextRequest) {
8
+ return handleCurationRoute(request, "context");
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleCurationRoute } from "../../../../../lib/curation-routes";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function POST(request: NextRequest) {
8
+ return handleCurationRoute(request, "priming");
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { handleCurationRoute } from "../../../../../lib/curation-routes";
3
+
4
+ export const dynamic = "force-dynamic";
5
+ export const runtime = "nodejs";
6
+
7
+ export function POST(request: NextRequest) {
8
+ return handleCurationRoute(request, "publication");
9
+ }
@@ -19,6 +19,7 @@ const USAGE = [
19
19
  " papyrus-infra synth --account-stack github-oidc [--out cdk.out] [--account A] [--region R]",
20
20
  " papyrus-infra print-buildspec --site infra/site.json --app cms-production|cms-staging|reader",
21
21
  " papyrus-infra deploy-edge --stack NAME --template FILE [--adoption-template FILE --adopt-resources FILE] [--parameter KEY=VALUE]... [--delete-function NAME] [--only-branch main] [--only-app-id ID] [--region R]",
22
+ " papyrus-infra compile-edge-function --static-dir web [--out FILE]",
22
23
  " papyrus-infra deploy --site infra/site.json [--only-branch main] [--only-app-id ID] [--out cdk.out] [--account A] [--region R]",
23
24
  ].join("\n");
24
25
 
@@ -36,11 +37,29 @@ const resolveEnvironmentOrExit = () => {
36
37
  }
37
38
  };
38
39
  const command = args[0];
39
- if (command !== "synth" && command !== "print-buildspec" && command !== "deploy" && command !== "deploy-edge") {
40
+ if (command !== "synth" && command !== "print-buildspec" && command !== "deploy" && command !== "deploy-edge" && command !== "compile-edge-function") {
40
41
  console.error(USAGE);
41
42
  process.exit(command ? 1 : 0);
42
43
  }
43
44
 
45
+ if (command === "compile-edge-function") {
46
+ // The viewer-request CloudFront Function with the site's projection.json rules compiled in: for an edge stack that is
47
+ // not deployed by the CMS backend, so that no site writes its own redirect function.
48
+ const staticDir = opt("static-dir");
49
+ if (!staticDir) {
50
+ console.error(USAGE);
51
+ process.exit(1);
52
+ }
53
+ const { compileRedirectFunctionSource, readEdgeConfig } = await import("../amplify/publication/redirect-rules.js");
54
+ const functionSource = fs.readFileSync(new URL("../amplify/publication/redirect-and-index.js", import.meta.url), "utf8");
55
+ const config = readEdgeConfig(staticDir);
56
+ const compiled = compileRedirectFunctionSource(functionSource, config.rules, { negotiateMarkdown: config.negotiateMarkdown });
57
+ const out = opt("out");
58
+ if (out) fs.writeFileSync(path.resolve(out), compiled);
59
+ else process.stdout.write(compiled);
60
+ process.exit(0);
61
+ }
62
+
44
63
  if (command === "deploy-edge") {
45
64
  const buildAppId = process.env.AWS_APP_ID;
46
65
  const buildBranch = process.env.AWS_BRANCH;