@anthusai/papyrus 2.0.0-next.1 → 2.0.0-next.11
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.
- package/amplify/data/resource.js +7 -0
- package/amplify/data/resource.ts +8 -0
- package/amplify/data/schema.js +10 -0
- package/amplify/data/schema.ts +11 -0
- package/amplify/functions/assignment-action/data-access.js +88 -0
- package/amplify/functions/assignment-action/data-access.ts +87 -0
- package/amplify/functions/content-actions/handler.py +7 -0
- package/amplify/functions/editorial-checks/handler.py +14 -0
- package/amplify/functions/editorial-checks/requirements.txt +1 -0
- package/amplify/functions/editorial-checks/resource.js +43 -0
- package/amplify/functions/editorial-checks/resource.ts +60 -0
- package/amplify/functions/editorial-checks/schema.js +92 -0
- package/amplify/functions/editorial-checks/schema.ts +94 -0
- package/amplify/functions/editorial-checks/wire.js +49 -0
- package/amplify/functions/editorial-checks/wire.ts +57 -0
- package/amplify/functions/model-attachment-upload/handler.ts +1 -0
- package/amplify/site-backend.js +9 -0
- package/amplify/site-backend.ts +15 -0
- package/components/editorial-checks-admin-panel.tsx +347 -0
- package/components/editorial-finding-card.tsx +162 -0
- package/components/editorial-findings-panel.tsx +91 -0
- package/components/editorial-preview-popover.tsx +103 -0
- package/components/markus-body-editor.tsx +213 -0
- package/components/markus-ir-preview.tsx +147 -6
- package/components/newsroom-article-editor.tsx +136 -11
- package/components/newsroom-board-view.tsx +6 -7
- package/components/newsroom-ops-shell.tsx +7 -3
- package/components/newsroom-organizer.css +57 -61
- package/components/newsroom-theme-fonts/broadsheet-green.css +10 -0
- package/components/newsroom-theme-fonts/broadsheet-green.tsx +6 -0
- package/components/newsroom-theme-fonts/newspaper-gothic.css +9 -0
- package/components/newsroom-theme-fonts/newspaper-gothic.tsx +6 -0
- package/components/newsroom-theme-fonts/night-desk.css +10 -0
- package/components/newsroom-theme-fonts/night-desk.tsx +6 -0
- package/components/newsroom-theme-fonts/refined-newsprint.css +11 -0
- package/components/newsroom-theme-fonts/refined-newsprint.tsx +6 -0
- package/components/newsroom-theme.css +39 -0
- package/components/newsroom-theme.tsx +33 -0
- package/components/reference-cyclotron-priming.tsx +12 -13
- package/components/reference-cyclotron-status.tsx +4 -4
- package/components/reference-cyclotron-suggestion.tsx +6 -6
- package/components/reference-live-agreement.tsx +10 -10
- package/components/reference-priming.tsx +1 -1
- package/components/reference-relevance-review.tsx +1 -1
- package/components/reference-section-suggestion.tsx +1 -1
- package/components/topic-steering-workspace.tsx +13 -2
- package/components/use-editorial-checks.ts +133 -0
- package/lib/cyclotron-suggestions.ts +9 -9
- package/lib/editorial-checks-backend.ts +16 -0
- package/lib/editorial-checks-client.ts +194 -0
- package/lib/editorial-checks-demo.ts +147 -0
- package/lib/editorial-checks-settings.ts +27 -0
- package/lib/editorial-checks.ts +171 -0
- package/lib/editorial-preview.ts +228 -0
- package/lib/newsroom-board.ts +27 -2
- package/lib/relevance-status.ts +4 -4
- package/lib/site-brand.ts +17 -0
- package/package.json +34 -2
- package/routes.manifest.json +1 -1
- package/components/newsroom-fonts/playfair-display-700.ttf +0 -0
- package/components/newsroom-fonts/playfair-display-900.ttf +0 -0
- package/components/newsroom-fonts/playfair-display-OFL.txt +0 -93
- package/components/newsroom-fonts/plus-jakarta-sans-400.ttf +0 -0
- package/components/newsroom-fonts/plus-jakarta-sans-600.ttf +0 -0
- package/components/newsroom-fonts/plus-jakarta-sans-OFL.txt +0 -93
- package/components/newsroom-newsprint-board.css +0 -7
package/amplify/data/resource.js
CHANGED
|
@@ -589,6 +589,12 @@ const schema = a.schema({
|
|
|
589
589
|
.returns(a.json())
|
|
590
590
|
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
591
591
|
.handler(a.handler.function(contentActions)),
|
|
592
|
+
intakePendingReferences: a
|
|
593
|
+
.mutation()
|
|
594
|
+
.arguments({ input: a.json().required() })
|
|
595
|
+
.returns(a.json())
|
|
596
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
597
|
+
.handler(a.handler.function(contentActions)),
|
|
592
598
|
saveItemDraft: a
|
|
593
599
|
.mutation()
|
|
594
600
|
.arguments({ input: a.json().required() })
|
|
@@ -2146,6 +2152,7 @@ const schema = a.schema({
|
|
|
2146
2152
|
allow.groups(contentWriteGroups),
|
|
2147
2153
|
]),
|
|
2148
2154
|
}).authorization((allow) => [
|
|
2155
|
+
allow.resource(assignmentAction).to(["query", "mutate"]),
|
|
2149
2156
|
allow.resource(categoryAction),
|
|
2150
2157
|
allow.resource(contentActions),
|
|
2151
2158
|
allow.resource(manageUserRole),
|
package/amplify/data/resource.ts
CHANGED
|
@@ -641,6 +641,13 @@ const schema = a.schema({
|
|
|
641
641
|
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
642
642
|
.handler(a.handler.function(contentActions)),
|
|
643
643
|
|
|
644
|
+
intakePendingReferences: a
|
|
645
|
+
.mutation()
|
|
646
|
+
.arguments({ input: a.json().required() })
|
|
647
|
+
.returns(a.json())
|
|
648
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
649
|
+
.handler(a.handler.function(contentActions)),
|
|
650
|
+
|
|
644
651
|
saveItemDraft: a
|
|
645
652
|
.mutation()
|
|
646
653
|
.arguments({ input: a.json().required() })
|
|
@@ -2271,6 +2278,7 @@ const schema = a.schema({
|
|
|
2271
2278
|
allow.groups(contentWriteGroups),
|
|
2272
2279
|
]),
|
|
2273
2280
|
}).authorization((allow) => [
|
|
2281
|
+
allow.resource(assignmentAction).to(["query", "mutate"]),
|
|
2274
2282
|
allow.resource(categoryAction),
|
|
2275
2283
|
allow.resource(contentActions),
|
|
2276
2284
|
allow.resource(manageUserRole),
|
package/amplify/data/schema.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { a } from "@aws-amplify/backend";
|
|
2
2
|
import { assignmentAction } from "../functions/assignment-action/resource.js";
|
|
3
3
|
import { categoryAction } from "../functions/category-action/resource.js";
|
|
4
|
+
import { editorialChecksSchemaDefinitions } from "../functions/editorial-checks/schema.js";
|
|
4
5
|
import { contentActions } from "../functions/content-actions/resource.js";
|
|
5
6
|
import { knowledgeQuery } from "../functions/knowledge-query/resource.js";
|
|
6
7
|
import { manageUserRole } from "../functions/manage-user-role/resource.js";
|
|
@@ -496,6 +497,12 @@ const baseDefinitions = {
|
|
|
496
497
|
.returns(a.json())
|
|
497
498
|
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
498
499
|
.handler(a.handler.function(contentActions)),
|
|
500
|
+
intakePendingReferences: a
|
|
501
|
+
.mutation()
|
|
502
|
+
.arguments({ input: a.json().required() })
|
|
503
|
+
.returns(a.json())
|
|
504
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
505
|
+
.handler(a.handler.function(contentActions)),
|
|
499
506
|
saveItemDraft: a
|
|
500
507
|
.mutation()
|
|
501
508
|
.arguments({ input: a.json().required() })
|
|
@@ -1987,8 +1994,10 @@ const baseDefinitions = {
|
|
|
1987
1994
|
allow.guest().to(["read"]),
|
|
1988
1995
|
allow.groups(contentWriteGroups),
|
|
1989
1996
|
]),
|
|
1997
|
+
...editorialChecksSchemaDefinitions,
|
|
1990
1998
|
};
|
|
1991
1999
|
export const schema = a.schema(baseDefinitions).authorization((allow) => [
|
|
2000
|
+
allow.resource(assignmentAction).to(["query", "mutate"]),
|
|
1992
2001
|
allow.resource(categoryAction),
|
|
1993
2002
|
allow.resource(contentActions),
|
|
1994
2003
|
allow.resource(manageUserRole),
|
|
@@ -1999,6 +2008,7 @@ export const schema = a.schema(baseDefinitions).authorization((allow) => [
|
|
|
1999
2008
|
/** The deployed schema of a site that enables plugins: the base definitions plus what each plugin contributes (docs/plugins.md). */
|
|
2000
2009
|
export function schemaWithPlugins(pluginDefinitions) {
|
|
2001
2010
|
return a.schema({ ...baseDefinitions, ...pluginDefinitions }).authorization((allow) => [
|
|
2011
|
+
allow.resource(assignmentAction).to(["query", "mutate"]),
|
|
2002
2012
|
allow.resource(categoryAction),
|
|
2003
2013
|
allow.resource(contentActions),
|
|
2004
2014
|
allow.resource(manageUserRole),
|
package/amplify/data/schema.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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";
|
|
4
5
|
import { contentActions } from "../functions/content-actions/resource";
|
|
5
6
|
import { knowledgeQuery } from "../functions/knowledge-query/resource";
|
|
6
7
|
import { manageUserRole } from "../functions/manage-user-role/resource";
|
|
@@ -542,6 +543,13 @@ const baseDefinitions = {
|
|
|
542
543
|
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
543
544
|
.handler(a.handler.function(contentActions)),
|
|
544
545
|
|
|
546
|
+
intakePendingReferences: a
|
|
547
|
+
.mutation()
|
|
548
|
+
.arguments({ input: a.json().required() })
|
|
549
|
+
.returns(a.json())
|
|
550
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
551
|
+
.handler(a.handler.function(contentActions)),
|
|
552
|
+
|
|
545
553
|
saveItemDraft: a
|
|
546
554
|
.mutation()
|
|
547
555
|
.arguments({ input: a.json().required() })
|
|
@@ -2105,9 +2113,11 @@ const baseDefinitions = {
|
|
|
2105
2113
|
allow.guest().to(["read"]),
|
|
2106
2114
|
allow.groups(contentWriteGroups),
|
|
2107
2115
|
]),
|
|
2116
|
+
...editorialChecksSchemaDefinitions,
|
|
2108
2117
|
};
|
|
2109
2118
|
|
|
2110
2119
|
export const schema = a.schema(baseDefinitions).authorization((allow) => [
|
|
2120
|
+
allow.resource(assignmentAction).to(["query", "mutate"]),
|
|
2111
2121
|
allow.resource(categoryAction),
|
|
2112
2122
|
allow.resource(contentActions),
|
|
2113
2123
|
allow.resource(manageUserRole),
|
|
@@ -2119,6 +2129,7 @@ export const schema = a.schema(baseDefinitions).authorization((allow) => [
|
|
|
2119
2129
|
/** The deployed schema of a site that enables plugins: the base definitions plus what each plugin contributes (docs/plugins.md). */
|
|
2120
2130
|
export function schemaWithPlugins(pluginDefinitions: Record<string, any>) {
|
|
2121
2131
|
return a.schema({ ...baseDefinitions, ...pluginDefinitions } as typeof baseDefinitions).authorization((allow) => [
|
|
2132
|
+
allow.resource(assignmentAction).to(["query", "mutate"]),
|
|
2122
2133
|
allow.resource(categoryAction),
|
|
2123
2134
|
allow.resource(contentActions),
|
|
2124
2135
|
allow.resource(manageUserRole),
|
|
@@ -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
|
+
}
|
|
@@ -23,6 +23,7 @@ from papyrus_content.rebuild_trigger import (
|
|
|
23
23
|
trigger_staging_build,
|
|
24
24
|
)
|
|
25
25
|
from papyrus_content.story_reporting import save_story_version
|
|
26
|
+
from papyrus_content.reference_intake import IntakeError, intake_references
|
|
26
27
|
from papyrus_content.publishing import (
|
|
27
28
|
ItemFields,
|
|
28
29
|
PublishError,
|
|
@@ -299,6 +300,12 @@ def handler(event, context):
|
|
|
299
300
|
arguments = parse_input(event["arguments"]["input"])
|
|
300
301
|
actor = actor_from_event(event)
|
|
301
302
|
try:
|
|
303
|
+
if field == "intakePendingReferences":
|
|
304
|
+
client, _claims = create_authoring_client()
|
|
305
|
+
try:
|
|
306
|
+
return intake_references(client, arguments, event.get("identity") or {})
|
|
307
|
+
except IntakeError as error:
|
|
308
|
+
return {"ok": False, "errors": [{"code": "intake-rejected", "message": str(error)}]}
|
|
302
309
|
if field == "deriveMarkus":
|
|
303
310
|
return derive_markus_action(arguments)
|
|
304
311
|
if field == "saveItemDraft":
|
|
@@ -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, 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["info"]["fieldName"], event.get("arguments") or {}, Services(store=GraphQLEditorialStore()))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
limatus[jev]>=0.32,<0.33
|
|
@@ -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; Limatus's classifier deadline (settings, default 20 s) keeps a check inside this. */
|
|
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"];
|
|
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; Limatus's classifier deadline (settings, default 20 s) keeps a check inside this. */
|
|
13
|
+
export const EDITORIAL_CHECKS_TIMEOUT_SECONDS = 28;
|
|
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"];
|
|
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
|
+
);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { a } from "@aws-amplify/backend";
|
|
2
|
+
import { editorialChecks } from "./resource.js";
|
|
3
|
+
const editors = ["editor", "admin"];
|
|
4
|
+
/**
|
|
5
|
+
* Core to every site's data API. Policies, settings and editors' decisions live in Papyrus; Limatus checks
|
|
6
|
+
* content against them. Editors read policies and record decisions; admins
|
|
7
|
+
* manage policies and settings. Only the function (IAM) calls Limatus.
|
|
8
|
+
*/
|
|
9
|
+
export const editorialChecksSchemaDefinitions = {
|
|
10
|
+
EditorialPolicy: a
|
|
11
|
+
.model({
|
|
12
|
+
id: a.id().required(),
|
|
13
|
+
policyKey: a.string().required(),
|
|
14
|
+
scope: a.string().required(),
|
|
15
|
+
version: a.integer().required(),
|
|
16
|
+
title: a.string().required(),
|
|
17
|
+
guidance: a.string().required(),
|
|
18
|
+
severity: a.string().required(),
|
|
19
|
+
enabled: a.boolean().required(),
|
|
20
|
+
detectors: a.json().required(),
|
|
21
|
+
examples: a.json(),
|
|
22
|
+
rewriteGuidance: a.string(),
|
|
23
|
+
source: a.string().required(),
|
|
24
|
+
})
|
|
25
|
+
.disableOperations(["subscriptions"])
|
|
26
|
+
.authorization((allow) => [allow.group("editor").to(["read"]), allow.group("admin")]),
|
|
27
|
+
EditorialCheckSettings: a
|
|
28
|
+
.model({
|
|
29
|
+
id: a.id().required(),
|
|
30
|
+
settings: a.json().required(),
|
|
31
|
+
})
|
|
32
|
+
.disableOperations(["subscriptions"])
|
|
33
|
+
.authorization((allow) => [allow.group("editor").to(["read"]), allow.group("admin")]),
|
|
34
|
+
EditorialFeedback: a
|
|
35
|
+
.model({
|
|
36
|
+
id: a.id().required(),
|
|
37
|
+
itemId: a.string().required(),
|
|
38
|
+
findingId: a.string().required(),
|
|
39
|
+
policyId: a.string().required(),
|
|
40
|
+
detectorId: a.string().required(),
|
|
41
|
+
lane: a.string().required(),
|
|
42
|
+
probability: a.float(),
|
|
43
|
+
decision: a.string().required(),
|
|
44
|
+
reason: a.string(),
|
|
45
|
+
text: a.string(),
|
|
46
|
+
})
|
|
47
|
+
.disableOperations(["subscriptions"])
|
|
48
|
+
.secondaryIndexes((index) => [index("itemId").queryField("listEditorialFeedbackByItem")])
|
|
49
|
+
.authorization((allow) => [allow.group("editor").to(["create", "read"]), allow.group("admin")]),
|
|
50
|
+
checkEditorial: a
|
|
51
|
+
.query()
|
|
52
|
+
.arguments({ body: a.string().required(), format: a.string(), section: a.string() })
|
|
53
|
+
.returns(a.json())
|
|
54
|
+
.authorization((allow) => [allow.groups(editors)])
|
|
55
|
+
.handler(a.handler.function(editorialChecks)),
|
|
56
|
+
suggestEditorialRewrite: a
|
|
57
|
+
.query()
|
|
58
|
+
.arguments({
|
|
59
|
+
body: a.string().required(),
|
|
60
|
+
format: a.string(),
|
|
61
|
+
section: a.string(),
|
|
62
|
+
finding: a.json(),
|
|
63
|
+
selection: a.json(),
|
|
64
|
+
note: a.string(),
|
|
65
|
+
})
|
|
66
|
+
.returns(a.json())
|
|
67
|
+
.authorization((allow) => [allow.groups(editors)])
|
|
68
|
+
.handler(a.handler.function(editorialChecks)),
|
|
69
|
+
draftEditorialQuestions: a
|
|
70
|
+
.query()
|
|
71
|
+
.arguments({ title: a.string().required(), guidance: a.string().required(), examples: a.json() })
|
|
72
|
+
.returns(a.json())
|
|
73
|
+
.authorization((allow) => [allow.group("admin")])
|
|
74
|
+
.handler(a.handler.function(editorialChecks)),
|
|
75
|
+
tryEditorialPolicy: a
|
|
76
|
+
.query()
|
|
77
|
+
.arguments({ policy: a.json().required(), text: a.string().required(), format: a.string() })
|
|
78
|
+
.returns(a.json())
|
|
79
|
+
.authorization((allow) => [allow.group("admin")])
|
|
80
|
+
.handler(a.handler.function(editorialChecks)),
|
|
81
|
+
importEditorialStarterPack: a
|
|
82
|
+
.mutation()
|
|
83
|
+
.arguments({ name: a.string().required() })
|
|
84
|
+
.returns(a.json())
|
|
85
|
+
.authorization((allow) => [allow.group("admin")])
|
|
86
|
+
.handler(a.handler.function(editorialChecks)),
|
|
87
|
+
recalibrateEditorialPolicies: a
|
|
88
|
+
.mutation()
|
|
89
|
+
.returns(a.json())
|
|
90
|
+
.authorization((allow) => [allow.group("admin")])
|
|
91
|
+
.handler(a.handler.function(editorialChecks)),
|
|
92
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { a } from "@aws-amplify/backend";
|
|
2
|
+
import { editorialChecks } from "./resource";
|
|
3
|
+
|
|
4
|
+
const editors = ["editor", "admin"];
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Core to every site's data API. Policies, settings and editors' decisions live in Papyrus; Limatus checks
|
|
8
|
+
* content against them. Editors read policies and record decisions; admins
|
|
9
|
+
* manage policies and settings. Only the function (IAM) calls Limatus.
|
|
10
|
+
*/
|
|
11
|
+
export const editorialChecksSchemaDefinitions = {
|
|
12
|
+
EditorialPolicy: a
|
|
13
|
+
.model({
|
|
14
|
+
id: a.id().required(),
|
|
15
|
+
policyKey: a.string().required(),
|
|
16
|
+
scope: a.string().required(),
|
|
17
|
+
version: a.integer().required(),
|
|
18
|
+
title: a.string().required(),
|
|
19
|
+
guidance: a.string().required(),
|
|
20
|
+
severity: a.string().required(),
|
|
21
|
+
enabled: a.boolean().required(),
|
|
22
|
+
detectors: a.json().required(),
|
|
23
|
+
examples: a.json(),
|
|
24
|
+
rewriteGuidance: a.string(),
|
|
25
|
+
source: a.string().required(),
|
|
26
|
+
})
|
|
27
|
+
.disableOperations(["subscriptions"])
|
|
28
|
+
.authorization((allow) => [allow.group("editor").to(["read"]), allow.group("admin")]),
|
|
29
|
+
EditorialCheckSettings: a
|
|
30
|
+
.model({
|
|
31
|
+
id: a.id().required(),
|
|
32
|
+
settings: a.json().required(),
|
|
33
|
+
})
|
|
34
|
+
.disableOperations(["subscriptions"])
|
|
35
|
+
.authorization((allow) => [allow.group("editor").to(["read"]), allow.group("admin")]),
|
|
36
|
+
EditorialFeedback: a
|
|
37
|
+
.model({
|
|
38
|
+
id: a.id().required(),
|
|
39
|
+
itemId: a.string().required(),
|
|
40
|
+
findingId: a.string().required(),
|
|
41
|
+
policyId: a.string().required(),
|
|
42
|
+
detectorId: a.string().required(),
|
|
43
|
+
lane: a.string().required(),
|
|
44
|
+
probability: a.float(),
|
|
45
|
+
decision: a.string().required(),
|
|
46
|
+
reason: a.string(),
|
|
47
|
+
text: a.string(),
|
|
48
|
+
})
|
|
49
|
+
.disableOperations(["subscriptions"])
|
|
50
|
+
.secondaryIndexes((index) => [index("itemId").queryField("listEditorialFeedbackByItem")])
|
|
51
|
+
.authorization((allow) => [allow.group("editor").to(["create", "read"]), allow.group("admin")]),
|
|
52
|
+
checkEditorial: a
|
|
53
|
+
.query()
|
|
54
|
+
.arguments({ body: a.string().required(), format: a.string(), section: a.string() })
|
|
55
|
+
.returns(a.json())
|
|
56
|
+
.authorization((allow) => [allow.groups(editors)])
|
|
57
|
+
.handler(a.handler.function(editorialChecks)),
|
|
58
|
+
suggestEditorialRewrite: a
|
|
59
|
+
.query()
|
|
60
|
+
.arguments({
|
|
61
|
+
body: a.string().required(),
|
|
62
|
+
format: a.string(),
|
|
63
|
+
section: a.string(),
|
|
64
|
+
finding: a.json(),
|
|
65
|
+
selection: a.json(),
|
|
66
|
+
note: a.string(),
|
|
67
|
+
})
|
|
68
|
+
.returns(a.json())
|
|
69
|
+
.authorization((allow) => [allow.groups(editors)])
|
|
70
|
+
.handler(a.handler.function(editorialChecks)),
|
|
71
|
+
draftEditorialQuestions: a
|
|
72
|
+
.query()
|
|
73
|
+
.arguments({ title: a.string().required(), guidance: a.string().required(), examples: a.json() })
|
|
74
|
+
.returns(a.json())
|
|
75
|
+
.authorization((allow) => [allow.group("admin")])
|
|
76
|
+
.handler(a.handler.function(editorialChecks)),
|
|
77
|
+
tryEditorialPolicy: a
|
|
78
|
+
.query()
|
|
79
|
+
.arguments({ policy: a.json().required(), text: a.string().required(), format: a.string() })
|
|
80
|
+
.returns(a.json())
|
|
81
|
+
.authorization((allow) => [allow.group("admin")])
|
|
82
|
+
.handler(a.handler.function(editorialChecks)),
|
|
83
|
+
importEditorialStarterPack: a
|
|
84
|
+
.mutation()
|
|
85
|
+
.arguments({ name: a.string().required() })
|
|
86
|
+
.returns(a.json())
|
|
87
|
+
.authorization((allow) => [allow.group("admin")])
|
|
88
|
+
.handler(a.handler.function(editorialChecks)),
|
|
89
|
+
recalibrateEditorialPolicies: a
|
|
90
|
+
.mutation()
|
|
91
|
+
.returns(a.json())
|
|
92
|
+
.authorization((allow) => [allow.group("admin")])
|
|
93
|
+
.handler(a.handler.function(editorialChecks)),
|
|
94
|
+
};
|