@anthusai/papyrus 2.0.0-next.4 → 2.0.0-next.41

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 (144) hide show
  1. package/amplify/data/resource.js +23 -3
  2. package/amplify/data/resource.ts +25 -3
  3. package/amplify/data/schema.js +2030 -1967
  4. package/amplify/data/schema.ts +73 -2
  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/assignment-action/handler.ts +1 -1
  8. package/amplify/functions/content-actions/handler.py +36 -0
  9. package/amplify/functions/content-actions/requirements.txt +1 -0
  10. package/amplify/functions/content-actions/resource.js +1 -1
  11. package/amplify/functions/content-actions/resource.ts +1 -1
  12. package/amplify/functions/editorial-checks/handler.py +14 -0
  13. package/amplify/functions/editorial-checks/requirements.txt +1 -0
  14. package/amplify/functions/editorial-checks/resource.js +43 -0
  15. package/amplify/functions/editorial-checks/resource.ts +60 -0
  16. package/amplify/functions/editorial-checks/schema.js +121 -0
  17. package/amplify/functions/editorial-checks/schema.ts +123 -0
  18. package/amplify/functions/editorial-checks/wire.js +53 -0
  19. package/amplify/functions/editorial-checks/wire.ts +61 -0
  20. package/amplify/functions/monitoring-view/handler.py +11 -0
  21. package/amplify/functions/monitoring-view/resource.js +24 -0
  22. package/amplify/functions/monitoring-view/resource.ts +34 -0
  23. package/amplify/functions/monitoring-view/schema.js +16 -0
  24. package/amplify/functions/monitoring-view/schema.ts +18 -0
  25. package/amplify/functions/monitoring-view/wire.js +37 -0
  26. package/amplify/functions/monitoring-view/wire.ts +47 -0
  27. package/amplify/functions/public-forms/core.js +247 -0
  28. package/amplify/functions/public-forms/core.ts +310 -0
  29. package/amplify/functions/public-forms/handler.ts +41 -0
  30. package/amplify/functions/public-forms/origins.js +35 -0
  31. package/amplify/functions/public-forms/origins.ts +43 -0
  32. package/amplify/functions/public-forms/resource.js +9 -0
  33. package/amplify/functions/public-forms/resource.ts +10 -0
  34. package/amplify/functions/public-forms/wire.js +25 -0
  35. package/amplify/functions/public-forms/wire.ts +32 -0
  36. package/amplify/functions/shared/editorial-context.ts +9 -3
  37. package/amplify/functions/shared/python-bundle.js +20 -14
  38. package/amplify/functions/shared/python-bundle.ts +25 -18
  39. package/amplify/plugins/reader-steering/config.js +110 -0
  40. package/amplify/plugins/reader-steering/config.ts +153 -0
  41. package/amplify/plugins/reader-steering/function/handler.py +544 -0
  42. package/amplify/plugins/reader-steering/function/resource.js +27 -0
  43. package/amplify/plugins/reader-steering/function/resource.ts +35 -0
  44. package/amplify/plugins/reader-steering/papyrus.plugin.js +103 -0
  45. package/amplify/plugins/reader-steering/papyrus.plugin.ts +122 -0
  46. package/amplify/plugins/reader-steering/schema.js +40 -0
  47. package/amplify/plugins/reader-steering/schema.ts +42 -0
  48. package/amplify/plugins/registry.js +2 -0
  49. package/amplify/plugins/registry.ts +2 -0
  50. package/amplify/plugins/types.ts +3 -0
  51. package/amplify/plugins/wire.js +1 -0
  52. package/amplify/plugins/wire.ts +1 -0
  53. package/amplify/publication/redirect-and-index.js +105 -0
  54. package/amplify/publication/redirect-rules.js +73 -0
  55. package/amplify/publication/redirect-rules.ts +93 -0
  56. package/amplify/publication/s3-projection.js +7 -1
  57. package/amplify/publication/s3-projection.ts +9 -1
  58. package/amplify/site-backend-config.ts +17 -0
  59. package/amplify/site-backend.js +36 -1
  60. package/amplify/site-backend.ts +44 -1
  61. package/app/api/newsroom/curation/context/route.ts +9 -0
  62. package/app/api/newsroom/curation/priming/route.ts +9 -0
  63. package/app/api/newsroom/curation/publication/route.ts +9 -0
  64. package/app/api/reader/handle/route.ts +9 -0
  65. package/app/api/reader/me/route.ts +9 -0
  66. package/app/api/reader/rating/route.ts +9 -0
  67. package/app/api/reader/retired/route.ts +9 -0
  68. package/app/api/reader/vote/route.ts +9 -0
  69. package/bin/papyrus-infra.mjs +20 -1
  70. package/components/editorial-checks-admin-panel.tsx +362 -0
  71. package/components/editorial-finding-card.tsx +162 -0
  72. package/components/editorial-findings-panel.tsx +91 -0
  73. package/components/editorial-preview-popover.tsx +103 -0
  74. package/components/markus-body-editor.tsx +213 -0
  75. package/components/markus-ir-preview.tsx +147 -6
  76. package/components/monitoring-overview-view.tsx +241 -0
  77. package/components/monitoring-panel.tsx +40 -0
  78. package/components/newsroom-article-editor.tsx +136 -11
  79. package/components/newsroom-board-view.tsx +6 -7
  80. package/components/newsroom-curation-bootstrap.tsx +45 -0
  81. package/components/newsroom-ops-shell.tsx +7 -3
  82. package/components/newsroom-organizer.css +74 -61
  83. package/components/newsroom-references-view.tsx +14 -14
  84. package/components/newsroom-theme-fonts/broadsheet-green.css +10 -0
  85. package/components/newsroom-theme-fonts/broadsheet-green.tsx +6 -0
  86. package/components/newsroom-theme-fonts/newspaper-gothic.css +9 -0
  87. package/components/newsroom-theme-fonts/newspaper-gothic.tsx +6 -0
  88. package/components/newsroom-theme-fonts/night-desk.css +10 -0
  89. package/components/newsroom-theme-fonts/night-desk.tsx +6 -0
  90. package/components/newsroom-theme-fonts/refined-newsprint.css +11 -0
  91. package/components/newsroom-theme-fonts/refined-newsprint.tsx +6 -0
  92. package/components/newsroom-theme.css +39 -0
  93. package/components/newsroom-theme.tsx +33 -0
  94. package/components/publication-cyclotron-panel.tsx +15 -10
  95. package/components/reference-add-entry.tsx +75 -0
  96. package/components/reference-curation-controls.tsx +31 -22
  97. package/components/reference-cyclotron-priming.tsx +58 -19
  98. package/components/reference-cyclotron-suggestion.tsx +1 -1
  99. package/components/reference-priming.tsx +14 -10
  100. package/components/reference-section-suggestion.tsx +1 -1
  101. package/components/topic-steering-workspace.tsx +27 -2
  102. package/components/use-editorial-checks.ts +133 -0
  103. package/infra/amplify-app-shell.js +90 -16
  104. package/infra/amplify-app-shell.ts +102 -18
  105. package/infra/index.js +1 -0
  106. package/infra/secure-string-parameter-handler.js +41 -0
  107. package/infra/secure-string-parameter-handler.ts +41 -0
  108. package/infra/secure-string-parameter.js +79 -0
  109. package/infra/secure-string-parameter.ts +91 -0
  110. package/infra/site-config.js +85 -6
  111. package/infra/site-config.ts +123 -6
  112. package/lib/category-repository.ts +11 -9
  113. package/lib/curation-routes.ts +345 -0
  114. package/lib/editorial-checks-backend.ts +16 -0
  115. package/lib/editorial-checks-client.ts +212 -0
  116. package/lib/editorial-checks-demo.ts +147 -0
  117. package/lib/editorial-checks-settings.ts +27 -0
  118. package/lib/editorial-checks.ts +173 -0
  119. package/lib/editorial-preview.ts +228 -0
  120. package/lib/monitoring-client.ts +29 -0
  121. package/lib/monitoring-overview-fixture.ts +51 -0
  122. package/lib/monitoring-overview.ts +145 -0
  123. package/lib/newsroom-board.ts +27 -2
  124. package/lib/prospectus-session.ts +15 -14
  125. package/lib/prospectus-sign-in-routes.ts +54 -13
  126. package/lib/prospectus-site-identity.ts +24 -1
  127. package/lib/reader-api.ts +143 -0
  128. package/lib/reader-retirement-route.ts +89 -0
  129. package/lib/reader-subject.md +29 -0
  130. package/lib/reader-subject.ts +53 -0
  131. package/lib/relevance-status.ts +2 -2
  132. package/lib/site-brand-tenant.ts +11 -6
  133. package/lib/site-brand.ts +23 -3
  134. package/lib/site-origin.ts +67 -0
  135. package/middleware.ts +14 -3
  136. package/package.json +56 -4
  137. package/routes.manifest.json +89 -1
  138. package/components/newsroom-fonts/playfair-display-700.ttf +0 -0
  139. package/components/newsroom-fonts/playfair-display-900.ttf +0 -0
  140. package/components/newsroom-fonts/playfair-display-OFL.txt +0 -93
  141. package/components/newsroom-fonts/plus-jakarta-sans-400.ttf +0 -0
  142. package/components/newsroom-fonts/plus-jakarta-sans-600.ttf +0 -0
  143. package/components/newsroom-fonts/plus-jakarta-sans-OFL.txt +0 -93
  144. package/components/newsroom-newsprint-board.css +0 -7
@@ -1,11 +1,14 @@
1
1
  import { type ClientSchema, a } from "@aws-amplify/backend";
2
2
  import { assignmentAction } from "../functions/assignment-action/resource";
3
3
  import { categoryAction } from "../functions/category-action/resource";
4
+ import { editorialChecksSchemaDefinitions } from "../functions/editorial-checks/schema";
5
+ import { monitoringViewSchemaDefinitions } from "../functions/monitoring-view/schema";
4
6
  import { contentActions } from "../functions/content-actions/resource";
5
7
  import { knowledgeQuery } from "../functions/knowledge-query/resource";
6
8
  import { manageUserRole } from "../functions/manage-user-role/resource";
7
9
  import { modelAttachmentUpload } from "../functions/model-attachment-upload/resource";
8
10
  import { newsroomSummary } from "../functions/newsroom-summary/resource";
11
+ import { publicForms } from "../functions/public-forms/resource";
9
12
  import { procedureAction } from "../functions/procedure-action/resource";
10
13
  import { readerSettings } from "../functions/reader-settings/resource";
11
14
 
@@ -23,7 +26,27 @@ const modelWithoutSubscriptions = <Fields extends Parameters<typeof a.model>[0]>
23
26
  a.model(fields).disableOperations(["subscriptions"]);
24
27
  const realtimeModel = <Fields extends Parameters<typeof a.model>[0]>(fields: Fields) => a.model(fields);
25
28
 
26
- const baseDefinitions = {
29
+ /**
30
+ * Model groups a site may leave out of its data API. CloudFormation creates at most 2,500 resources in one stack
31
+ * operation, nested stacks included, and the full model set exceeds that on a fresh create. A first deploy that
32
+ * excludes a group fits; a second deploy without the exclusion adds the models as an ordinary update.
33
+ */
34
+ export const OPTIONAL_MODEL_GROUPS = {
35
+ editions: ["Edition", "EditionItem", "PublishedEdition", "PublishedEditionItem"],
36
+ } as const;
37
+
38
+ export type OptionalModelGroup = keyof typeof OPTIONAL_MODEL_GROUPS;
39
+
40
+ export function assertKnownModelGroups(groups: readonly string[]): asserts groups is readonly OptionalModelGroup[] {
41
+ for (const group of groups) {
42
+ if (!(group in OPTIONAL_MODEL_GROUPS)) {
43
+ throw new Error(`Unknown model group "${group}"; known groups: ${Object.keys(OPTIONAL_MODEL_GROUPS).join(", ")}`);
44
+ }
45
+ }
46
+ }
47
+
48
+ function buildBaseDefinitions(includeEditions: boolean) {
49
+ const definitions = {
27
50
  UserProfile: modelWithoutSubscriptions({
28
51
  id: a.id().required(),
29
52
  email: a.email(),
@@ -542,6 +565,13 @@ const baseDefinitions = {
542
565
  .authorization((allow) => [allow.groups(contentWriteGroups)])
543
566
  .handler(a.handler.function(contentActions)),
544
567
 
568
+ intakePendingReferences: a
569
+ .mutation()
570
+ .arguments({ input: a.json().required() })
571
+ .returns(a.json())
572
+ .authorization((allow) => [allow.groups(contentWriteGroups)])
573
+ .handler(a.handler.function(contentActions)),
574
+
545
575
  saveItemDraft: a
546
576
  .mutation()
547
577
  .arguments({ input: a.json().required() })
@@ -556,6 +586,13 @@ const baseDefinitions = {
556
586
  .authorization((allow) => [allow.groups(contentWriteGroups)])
557
587
  .handler(a.handler.function(contentActions)),
558
588
 
589
+ moveStoryStage: a
590
+ .mutation()
591
+ .arguments({ input: a.json().required() })
592
+ .returns(a.json())
593
+ .authorization((allow) => [allow.groups(contentWriteGroups)])
594
+ .handler(a.handler.function(contentActions)),
595
+
559
596
  unpublishItem: a
560
597
  .mutation()
561
598
  .arguments({ input: a.json().required() })
@@ -817,6 +854,7 @@ const baseDefinitions = {
817
854
  storyLineageId: a.id(),
818
855
  assignmentId: a.id(),
819
856
  maxCharacters: a.integer(),
857
+ optionalPublicationDoctrine: a.boolean(),
820
858
  })
821
859
  .returns(a.json().required())
822
860
  .authorization((allow) => [allow.groups(categoryWriteGroups)])
@@ -1782,7 +1820,7 @@ const baseDefinitions = {
1782
1820
  updatedAt: a.datetime(),
1783
1821
  tags: a.hasMany("ItemTag", "itemId"),
1784
1822
  mediaAssets: a.hasMany("MediaAsset", "itemId"),
1785
- editionItems: a.hasMany("EditionItem", "itemId"),
1823
+ ...(includeEditions ? { editionItems: a.hasMany("EditionItem", "itemId") } : {}),
1786
1824
  })
1787
1825
  .secondaryIndexes((index) => [
1788
1826
  index("lineageId").sortKeys(["versionNumber"]).queryField("listItemsByLineageAndVersion"),
@@ -2105,26 +2143,59 @@ const baseDefinitions = {
2105
2143
  allow.guest().to(["read"]),
2106
2144
  allow.groups(contentWriteGroups),
2107
2145
  ]),
2146
+ ...editorialChecksSchemaDefinitions,
2147
+ ...monitoringViewSchemaDefinitions,
2108
2148
  };
2149
+ if (!includeEditions) {
2150
+ const mutableDefinitions = definitions as Record<string, unknown>;
2151
+ for (const modelName of OPTIONAL_MODEL_GROUPS.editions) delete mutableDefinitions[modelName];
2152
+ }
2153
+ return definitions;
2154
+ }
2155
+
2156
+ export function baseModelNames(excludedGroups: readonly OptionalModelGroup[] = []): string[] {
2157
+ return Object.keys(buildBaseDefinitions(!excludedGroups.includes("editions")));
2158
+ }
2159
+
2160
+ const baseDefinitions = buildBaseDefinitions(true);
2109
2161
 
2110
2162
  export const schema = a.schema(baseDefinitions).authorization((allow) => [
2163
+ allow.resource(assignmentAction).to(["query", "mutate"]),
2111
2164
  allow.resource(categoryAction),
2112
2165
  allow.resource(contentActions),
2113
2166
  allow.resource(manageUserRole),
2114
2167
  allow.resource(modelAttachmentUpload),
2115
2168
  allow.resource(newsroomSummary),
2116
2169
  allow.resource(procedureAction),
2170
+ allow.resource(publicForms).to(["mutate"]),
2117
2171
  ]);
2118
2172
 
2119
2173
  /** The deployed schema of a site that enables plugins: the base definitions plus what each plugin contributes (docs/plugins.md). */
2120
2174
  export function schemaWithPlugins(pluginDefinitions: Record<string, any>) {
2121
2175
  return a.schema({ ...baseDefinitions, ...pluginDefinitions } as typeof baseDefinitions).authorization((allow) => [
2176
+ allow.resource(assignmentAction).to(["query", "mutate"]),
2177
+ allow.resource(categoryAction),
2178
+ allow.resource(contentActions),
2179
+ allow.resource(manageUserRole),
2180
+ allow.resource(modelAttachmentUpload),
2181
+ allow.resource(newsroomSummary),
2182
+ allow.resource(procedureAction),
2183
+ ]);
2184
+ }
2185
+
2186
+ /** The schema of a site that leaves optional model groups out of its first deploy. */
2187
+ export function schemaExcludingGroups(excludedGroups: readonly OptionalModelGroup[], pluginDefinitions: Record<string, any> = {}) {
2188
+ const definitions = buildBaseDefinitions(!excludedGroups.includes("editions")) as unknown as typeof baseDefinitions;
2189
+ const hasPlugins = Object.keys(pluginDefinitions).length > 0;
2190
+ return a.schema({ ...definitions, ...pluginDefinitions } as typeof baseDefinitions).authorization((allow) => [
2191
+ allow.resource(assignmentAction).to(["query", "mutate"]),
2122
2192
  allow.resource(categoryAction),
2123
2193
  allow.resource(contentActions),
2124
2194
  allow.resource(manageUserRole),
2125
2195
  allow.resource(modelAttachmentUpload),
2126
2196
  allow.resource(newsroomSummary),
2127
2197
  allow.resource(procedureAction),
2198
+ ...(hasPlugins ? [] : [allow.resource(publicForms).to(["mutate"])]),
2128
2199
  ]);
2129
2200
  }
2130
2201
 
@@ -0,0 +1,88 @@
1
+ import { Aspects, Stack } from "aws-cdk-lib";
2
+ import { CfnPolicy } from "aws-cdk-lib/aws-iam";
3
+ /** Lifecycle/context calls, including the fixed reference registration CLI's provenance operations. */
4
+ export const ASSIGNMENT_DATA_FIELDS = [
5
+ "Query/fields/getAssignment",
6
+ "Query/fields/getKnowledgeRawPayload",
7
+ "Query/fields/getProcedureRun",
8
+ "Query/fields/getProcedureVersion",
9
+ "Query/fields/getProcedureDefinition",
10
+ "Query/fields/getModelAttachment",
11
+ "Query/fields/getNewsroomSection",
12
+ "Query/fields/getKnowledgeCorpus",
13
+ "Query/fields/getKnowledgeImportRun",
14
+ "Query/fields/getReference",
15
+ "Query/fields/getReferenceAttachment",
16
+ "Query/fields/getMessage",
17
+ "Query/fields/getSemanticRelation",
18
+ "Query/fields/getSemanticNode",
19
+ "Query/fields/listReferences",
20
+ "Query/fields/listReferenceAttachments",
21
+ "Query/fields/listSemanticRelations",
22
+ "Query/fields/listKnowledgeCorporaByRoleAndName",
23
+ "Query/fields/listItemsByLineageAndVersion",
24
+ "Query/fields/itemBySlug",
25
+ "Query/fields/itemByTenantSlug",
26
+ "Query/fields/listModelAttachmentsByOwnerRoleAndSortKey",
27
+ "Query/fields/listAssignmentsByQueueStatusAndPriority",
28
+ "Query/fields/listSemanticRelationsByObjectState",
29
+ "Query/fields/listSemanticRelationsBySubjectState",
30
+ "Query/fields/listAssignmentEventsByAssignmentAndCreatedAt",
31
+ "Mutation/fields/createAssignment",
32
+ "Mutation/fields/updateAssignment",
33
+ "Mutation/fields/createAssignmentEvent",
34
+ "Mutation/fields/createProcedureRun",
35
+ "Mutation/fields/updateProcedureRun",
36
+ "Mutation/fields/createKnowledgeCorpus",
37
+ "Mutation/fields/updateKnowledgeCorpus",
38
+ "Mutation/fields/createKnowledgeImportRun",
39
+ "Mutation/fields/updateKnowledgeImportRun",
40
+ "Mutation/fields/createKnowledgeRawPayload",
41
+ "Mutation/fields/updateKnowledgeRawPayload",
42
+ "Mutation/fields/createReference",
43
+ "Mutation/fields/updateReference",
44
+ "Mutation/fields/createReferenceAttachment",
45
+ "Mutation/fields/updateReferenceAttachment",
46
+ "Mutation/fields/createMessage",
47
+ "Mutation/fields/updateMessage",
48
+ "Mutation/fields/createSemanticRelation",
49
+ "Mutation/fields/updateSemanticRelation",
50
+ "Mutation/fields/createSemanticNode",
51
+ "Mutation/fields/updateSemanticNode",
52
+ "Mutation/fields/createModelAttachment",
53
+ "Mutation/fields/updateModelAttachment",
54
+ "Mutation/fields/createModelAttachmentUpload",
55
+ "Mutation/fields/completeModelAttachmentUpload",
56
+ "Mutation/fields/createModelAttachmentDownload",
57
+ "Mutation/fields/updateNewsroomSummary",
58
+ ];
59
+ /** Keep the SDK's generated configuration, replacing only this role's GraphQL grants at synthesis. */
60
+ export function scopeAssignmentDataAccess(lambda, graphqlApiArn) {
61
+ if (!lambda.role)
62
+ throw new Error("Assignment function needs an execution role.");
63
+ const roleName = lambda.role.roleName;
64
+ const resources = ASSIGNMENT_DATA_FIELDS.map((field) => `${graphqlApiArn}/types/${field}`);
65
+ Aspects.of(Stack.of(lambda)).add({
66
+ visit(node) {
67
+ if (!(node instanceof CfnPolicy))
68
+ return;
69
+ const stack = Stack.of(node);
70
+ const roles = stack.resolve(node.roles);
71
+ if (!roles?.some((role) => JSON.stringify(role) === JSON.stringify(stack.resolve(roleName))))
72
+ return;
73
+ const document = stack.resolve(node.policyDocument);
74
+ document.Statement.forEach((statement, index) => {
75
+ const actions = Array.isArray(statement.Action) ? statement.Action : [statement.Action];
76
+ if (actions.includes("*") || actions.includes("appsync:*")) {
77
+ throw new Error("Unexpected wildcard assignment action; review permissions before synthesis.");
78
+ }
79
+ if (!actions.includes("appsync:GraphQL"))
80
+ return;
81
+ if (statement.Effect !== "Allow" || actions.length !== 1 || statement.NotResource) {
82
+ throw new Error("Unexpected assignment GraphQL policy; review field scoping before synthesis.");
83
+ }
84
+ node.addPropertyOverride(`PolicyDocument.Statement.${index}.Resource`, resources);
85
+ });
86
+ },
87
+ });
88
+ }
@@ -0,0 +1,87 @@
1
+ import { Aspects, Stack } from "aws-cdk-lib";
2
+ import { CfnPolicy } from "aws-cdk-lib/aws-iam";
3
+ import type { IFunction } from "aws-cdk-lib/aws-lambda";
4
+
5
+ /** Lifecycle/context calls, including the fixed reference registration CLI's provenance operations. */
6
+ export const ASSIGNMENT_DATA_FIELDS = [
7
+ "Query/fields/getAssignment",
8
+ "Query/fields/getKnowledgeRawPayload",
9
+ "Query/fields/getProcedureRun",
10
+ "Query/fields/getProcedureVersion",
11
+ "Query/fields/getProcedureDefinition",
12
+ "Query/fields/getModelAttachment",
13
+ "Query/fields/getNewsroomSection",
14
+ "Query/fields/getKnowledgeCorpus",
15
+ "Query/fields/getKnowledgeImportRun",
16
+ "Query/fields/getReference",
17
+ "Query/fields/getReferenceAttachment",
18
+ "Query/fields/getMessage",
19
+ "Query/fields/getSemanticRelation",
20
+ "Query/fields/getSemanticNode",
21
+ "Query/fields/listReferences",
22
+ "Query/fields/listReferenceAttachments",
23
+ "Query/fields/listSemanticRelations",
24
+ "Query/fields/listKnowledgeCorporaByRoleAndName",
25
+ "Query/fields/listItemsByLineageAndVersion",
26
+ "Query/fields/itemBySlug",
27
+ "Query/fields/itemByTenantSlug",
28
+ "Query/fields/listModelAttachmentsByOwnerRoleAndSortKey",
29
+ "Query/fields/listAssignmentsByQueueStatusAndPriority",
30
+ "Query/fields/listSemanticRelationsByObjectState",
31
+ "Query/fields/listSemanticRelationsBySubjectState",
32
+ "Query/fields/listAssignmentEventsByAssignmentAndCreatedAt",
33
+ "Mutation/fields/createAssignment",
34
+ "Mutation/fields/updateAssignment",
35
+ "Mutation/fields/createAssignmentEvent",
36
+ "Mutation/fields/createProcedureRun",
37
+ "Mutation/fields/updateProcedureRun",
38
+ "Mutation/fields/createKnowledgeCorpus",
39
+ "Mutation/fields/updateKnowledgeCorpus",
40
+ "Mutation/fields/createKnowledgeImportRun",
41
+ "Mutation/fields/updateKnowledgeImportRun",
42
+ "Mutation/fields/createKnowledgeRawPayload",
43
+ "Mutation/fields/updateKnowledgeRawPayload",
44
+ "Mutation/fields/createReference",
45
+ "Mutation/fields/updateReference",
46
+ "Mutation/fields/createReferenceAttachment",
47
+ "Mutation/fields/updateReferenceAttachment",
48
+ "Mutation/fields/createMessage",
49
+ "Mutation/fields/updateMessage",
50
+ "Mutation/fields/createSemanticRelation",
51
+ "Mutation/fields/updateSemanticRelation",
52
+ "Mutation/fields/createSemanticNode",
53
+ "Mutation/fields/updateSemanticNode",
54
+ "Mutation/fields/createModelAttachment",
55
+ "Mutation/fields/updateModelAttachment",
56
+ "Mutation/fields/createModelAttachmentUpload",
57
+ "Mutation/fields/completeModelAttachmentUpload",
58
+ "Mutation/fields/createModelAttachmentDownload",
59
+ "Mutation/fields/updateNewsroomSummary",
60
+ ] as const;
61
+
62
+ /** Keep the SDK's generated configuration, replacing only this role's GraphQL grants at synthesis. */
63
+ export function scopeAssignmentDataAccess(lambda: IFunction, graphqlApiArn: string): void {
64
+ if (!lambda.role) throw new Error("Assignment function needs an execution role.");
65
+ const roleName = lambda.role.roleName;
66
+ const resources = ASSIGNMENT_DATA_FIELDS.map((field) => `${graphqlApiArn}/types/${field}`);
67
+ Aspects.of(Stack.of(lambda)).add({
68
+ visit(node) {
69
+ if (!(node instanceof CfnPolicy)) return;
70
+ const stack = Stack.of(node);
71
+ const roles = stack.resolve(node.roles) as unknown[] | undefined;
72
+ if (!roles?.some((role) => JSON.stringify(role) === JSON.stringify(stack.resolve(roleName)))) return;
73
+ const document = stack.resolve(node.policyDocument) as { Statement: Array<Record<string, unknown>> };
74
+ document.Statement.forEach((statement, index) => {
75
+ const actions = Array.isArray(statement.Action) ? statement.Action : [statement.Action];
76
+ if (actions.includes("*") || actions.includes("appsync:*")) {
77
+ throw new Error("Unexpected wildcard assignment action; review permissions before synthesis.");
78
+ }
79
+ if (!actions.includes("appsync:GraphQL")) return;
80
+ if (statement.Effect !== "Allow" || actions.length !== 1 || statement.NotResource) {
81
+ throw new Error("Unexpected assignment GraphQL policy; review field scoping before synthesis.");
82
+ }
83
+ node.addPropertyOverride(`PolicyDocument.Statement.${index}.Resource`, resources);
84
+ });
85
+ },
86
+ });
87
+ }
@@ -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,
@@ -9,6 +9,7 @@ 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
11
  from papyrus_content.projection.engine import project, project_all, project_item_pages
12
+ from papyrus_content.reader_steering.task import TASK_NAME as READER_SNAPSHOT_TASK, run_reader_snapshot
12
13
  from papyrus_content.projection.runtime import (
13
14
  DEAD_LETTER_QUEUE_ENV,
14
15
  context_from_environment,
@@ -23,6 +24,9 @@ from papyrus_content.rebuild_trigger import (
23
24
  trigger_staging_build,
24
25
  )
25
26
  from papyrus_content.story_reporting import save_story_version
27
+ from papyrus_content.publication import ensure_publication_from_environment
28
+ from papyrus_content.editorial_checks import GraphQLEditorialStore, ensure_default_policies
29
+ from papyrus_content.reference_intake import IntakeError, intake_references
26
30
  from papyrus_content.publishing import (
27
31
  ItemFields,
28
32
  PublishError,
@@ -148,8 +152,19 @@ def schedule_unpurged_invalidation(keys: list[str]) -> None:
148
152
  schedule_projection_task({"task": "invalidate", "keys": keys}, "edge invalidation failed")
149
153
 
150
154
 
155
+ def ensure_default_editorial_policies(client) -> None:
156
+ """A deployed site starts with working editorial policies. Never fails the deploy: a problem is logged."""
157
+ try:
158
+ result = ensure_default_policies(GraphQLEditorialStore(execute=client.graphql))
159
+ print(json.dumps({"defaultEditorialPolicies": result}))
160
+ except Exception as error:
161
+ print(json.dumps({"defaultEditorialPoliciesError": str(error)[:500]}))
162
+
163
+
151
164
  def run_deploy_render_task(client) -> dict:
152
165
  """Render every published page after a backend deploy, then report drift (a dry run: nothing is deleted)."""
166
+ ensure_publication_from_environment(client)
167
+ ensure_default_editorial_policies(client)
153
168
  context = projection_context(client)
154
169
  report = deploy_render(context)
155
170
  print(drift_metric_line(report))
@@ -173,6 +188,8 @@ def run_projection_task(task: dict) -> dict:
173
188
  report = project_all(projection_context(client))
174
189
  elif task.get("task") == "deploy-render":
175
190
  return run_deploy_render_task(client)
191
+ elif task.get("task") == READER_SNAPSHOT_TASK:
192
+ return run_reader_snapshot(projection_context(client), client).to_dict()
176
193
  elif task.get("task") == "invalidate":
177
194
  invalidate = projection_context(client).invalidate
178
195
  if invalidate is None:
@@ -283,6 +300,17 @@ def unpublish_item_action(arguments: dict, actor: str) -> dict:
283
300
  return response
284
301
 
285
302
 
303
+ def move_story_stage_action(arguments: dict, actor: str) -> dict:
304
+ from papyrus_content.story_reporting import move_story_stage
305
+
306
+ client, _claims = create_authoring_client()
307
+ try:
308
+ result = move_story_stage(client, arguments["lineageId"], arguments["stage"], actor=actor, reason=arguments.get("reason"))
309
+ except ValueError as error:
310
+ return {"ok": False, "errors": [{"code": "invalid-stage", "message": str(error), "line": None}]}
311
+ return {"ok": True, **result}
312
+
313
+
286
314
  def request_staging_build_action() -> dict:
287
315
  cms_app_id = (os.environ.get("PAPYRUS_CMS_AMPLIFY_APP_ID") or "").strip()
288
316
  if not cms_app_id:
@@ -299,12 +327,20 @@ def handler(event, context):
299
327
  arguments = parse_input(event["arguments"]["input"])
300
328
  actor = actor_from_event(event)
301
329
  try:
330
+ if field == "intakePendingReferences":
331
+ client, _claims = create_authoring_client()
332
+ try:
333
+ return intake_references(client, arguments, event.get("identity") or {})
334
+ except IntakeError as error:
335
+ return {"ok": False, "errors": [{"code": "intake-rejected", "message": str(error)}]}
302
336
  if field == "deriveMarkus":
303
337
  return derive_markus_action(arguments)
304
338
  if field == "saveItemDraft":
305
339
  return save_item_draft_action(arguments, actor)
306
340
  if field == "publishItem":
307
341
  return publish_item_action(arguments, actor)
342
+ if field == "moveStoryStage":
343
+ return move_story_stage_action(arguments, actor)
308
344
  if field == "unpublishItem":
309
345
  return unpublish_item_action(arguments, actor)
310
346
  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");
@@ -0,0 +1,14 @@
1
+ """The editorial-checks function: routes its GraphQL fields to papyrus_content.editorial_checks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from papyrus_content.editorial_checks import GraphQLEditorialStore, Services, event_field_name, handle, load_provider_keys
8
+
9
+ # Amplify passes secrets to Python functions as placeholders; resolve them once per cold start.
10
+ load_provider_keys()
11
+
12
+
13
+ def handler(event: dict[str, Any], _context: Any = None) -> dict[str, Any]:
14
+ return handle(event_field_name(event), event.get("arguments") or {}, Services(store=GraphQLEditorialStore()))
@@ -0,0 +1 @@
1
+ humanus[jev]>=0.33,<0.34
@@ -0,0 +1,43 @@
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 { execFileSync } from "node:child_process";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { pythonLambdaCode } from "../shared/python-bundle.js";
8
+ const functionDirectory = path.dirname(fileURLToPath(import.meta.url));
9
+ /** AppSync gives a resolver 30 seconds; Humanus's classifier deadline (settings, default 20 s) keeps a check inside this. */
10
+ export const EDITORIAL_CHECKS_TIMEOUT_SECONDS = 28;
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
+ function installRequirements(outputDir) {
14
+ execFileSync("python3", [
15
+ "-m", "pip", "install", "-r", path.join(functionDirectory, "requirements.txt"), "-t", outputDir, "--no-cache-dir",
16
+ "--platform", "manylinux2014_aarch64", "--implementation", "cp", "--python-version", "3.12",
17
+ "--only-binary=:all:", "--ignore-requires-python",
18
+ ], { stdio: "inherit" });
19
+ }
20
+ export const editorialChecks = defineFunction((scope) => new Function(scope, "papyrus-editorial-checks", {
21
+ runtime: Runtime.PYTHON_3_12,
22
+ architecture: Architecture.ARM_64,
23
+ handler: "handler.handler",
24
+ timeout: Duration.seconds(EDITORIAL_CHECKS_TIMEOUT_SECONDS),
25
+ memorySize: 1024,
26
+ code: pythonLambdaCode({
27
+ functionDir: "editorial-checks",
28
+ modules: ["papyrus_content"],
29
+ requirements: editorialChecksRequirements,
30
+ noDeps: true,
31
+ checkoutExtras: installRequirements,
32
+ }, [
33
+ "bash",
34
+ "-c",
35
+ [
36
+ "set -euo pipefail",
37
+ "mkdir -p /asset-output/papyrus_content",
38
+ "cp amplify/functions/editorial-checks/handler.py /asset-output/handler.py",
39
+ "cp -R src/papyrus_content/. /asset-output/papyrus_content/",
40
+ "python3 -m pip install -r amplify/functions/editorial-checks/requirements.txt -t /asset-output --no-cache-dir",
41
+ ].join(" && "),
42
+ ]),
43
+ }), { resourceGroupName: "data" });
@@ -0,0 +1,60 @@
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 { execFileSync } from "node:child_process";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { pythonLambdaCode } from "../shared/python-bundle";
9
+
10
+ const functionDirectory = path.dirname(fileURLToPath(import.meta.url));
11
+
12
+ /** AppSync gives a resolver 30 seconds; Humanus's classifier deadline (settings, default 20 s) keeps a check inside this. */
13
+ export const EDITORIAL_CHECKS_TIMEOUT_SECONDS = 28;
14
+
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
+
18
+ function installRequirements(outputDir: string): void {
19
+ execFileSync(
20
+ "python3",
21
+ [
22
+ "-m", "pip", "install", "-r", path.join(functionDirectory, "requirements.txt"), "-t", outputDir, "--no-cache-dir",
23
+ "--platform", "manylinux2014_aarch64", "--implementation", "cp", "--python-version", "3.12",
24
+ "--only-binary=:all:", "--ignore-requires-python",
25
+ ],
26
+ { stdio: "inherit" },
27
+ );
28
+ }
29
+
30
+ export const editorialChecks = defineFunction(
31
+ (scope: Construct) =>
32
+ new Function(scope, "papyrus-editorial-checks", {
33
+ runtime: Runtime.PYTHON_3_12,
34
+ architecture: Architecture.ARM_64,
35
+ handler: "handler.handler",
36
+ timeout: Duration.seconds(EDITORIAL_CHECKS_TIMEOUT_SECONDS),
37
+ memorySize: 1024,
38
+ code: pythonLambdaCode(
39
+ {
40
+ functionDir: "editorial-checks",
41
+ modules: ["papyrus_content"],
42
+ requirements: editorialChecksRequirements,
43
+ noDeps: true,
44
+ checkoutExtras: installRequirements,
45
+ },
46
+ [
47
+ "bash",
48
+ "-c",
49
+ [
50
+ "set -euo pipefail",
51
+ "mkdir -p /asset-output/papyrus_content",
52
+ "cp amplify/functions/editorial-checks/handler.py /asset-output/handler.py",
53
+ "cp -R src/papyrus_content/. /asset-output/papyrus_content/",
54
+ "python3 -m pip install -r amplify/functions/editorial-checks/requirements.txt -t /asset-output --no-cache-dir",
55
+ ].join(" && "),
56
+ ],
57
+ ),
58
+ }),
59
+ { resourceGroupName: "data" },
60
+ );