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