@anthusai/papyrus 5.31.0 → 5.33.0
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 +69 -0
- package/amplify/data/resource.ts +73 -0
- package/amplify/data/schema.js +68 -0
- package/amplify/data/schema.ts +72 -0
- 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 +25 -0
- package/amplify/functions/automation-view/schema.ts +27 -0
- package/amplify/functions/automation-view/wire.js +56 -0
- package/amplify/functions/automation-view/wire.ts +76 -0
- package/amplify/plugins/reader-steering/config.js +3 -1
- package/amplify/plugins/reader-steering/config.ts +11 -1
- package/amplify/plugins/reader-steering/papyrus.plugin.js +2 -0
- package/amplify/plugins/reader-steering/papyrus.plugin.ts +2 -0
- package/amplify/site-backend.js +4 -0
- package/amplify/site-backend.ts +4 -0
- package/components/automation-panel.tsx +49 -0
- package/components/automation-run-now.tsx +97 -0
- package/components/automation-view.tsx +192 -0
- package/components/newsroom-ops-overview.tsx +18 -0
- package/components/notification-center.tsx +262 -0
- package/components/topic-steering-workspace.tsx +17 -3
- package/lib/automation-client.ts +54 -0
- package/lib/automation-fixture.ts +93 -0
- package/lib/automation-overview.ts +232 -0
- package/lib/review-client.ts +99 -0
- package/lib/review-fixture.ts +45 -0
- package/lib/review-requests.ts +238 -0
- package/package.json +11 -1
- package/routes.manifest.json +1 -1
package/amplify/data/resource.js
CHANGED
|
@@ -1152,6 +1152,8 @@ const schema = a.schema({
|
|
|
1152
1152
|
error: a.json(),
|
|
1153
1153
|
attempt: a.integer(),
|
|
1154
1154
|
newsroomFeedKey: a.string(),
|
|
1155
|
+
usage: a.json(),
|
|
1156
|
+
costUsd: a.float(),
|
|
1155
1157
|
})
|
|
1156
1158
|
.secondaryIndexes((index) => [
|
|
1157
1159
|
index("procedureId").sortKeys(["requestedAt"]).queryField("listProcedureRunsByProcedureAndRequestedAt"),
|
|
@@ -1163,6 +1165,73 @@ const schema = a.schema({
|
|
|
1163
1165
|
allow.group("editor").to(["read"]),
|
|
1164
1166
|
allow.group(adminGroup).to(authoringOperations),
|
|
1165
1167
|
]),
|
|
1168
|
+
Automation: a
|
|
1169
|
+
.model({
|
|
1170
|
+
id: a.id().required(),
|
|
1171
|
+
title: a.string().required(),
|
|
1172
|
+
description: a.string(),
|
|
1173
|
+
enabled: a.boolean().required(),
|
|
1174
|
+
trigger: a.json().required(),
|
|
1175
|
+
procedureKey: a.string().required(),
|
|
1176
|
+
inputTemplate: a.json(),
|
|
1177
|
+
limits: a.json(),
|
|
1178
|
+
createdBy: a.string(),
|
|
1179
|
+
createdAt: a.datetime().required(),
|
|
1180
|
+
updatedBy: a.string(),
|
|
1181
|
+
updatedAt: a.datetime().required(),
|
|
1182
|
+
})
|
|
1183
|
+
.authorization((allow) => [
|
|
1184
|
+
allow.group("editor").to(["read", "create", "update"]),
|
|
1185
|
+
allow.group(adminGroup).to(authoringOperations),
|
|
1186
|
+
]),
|
|
1187
|
+
/** A human review point raised by a step on any item (PPY-ab6a50). Review is not a story state. */
|
|
1188
|
+
ReviewRequest: a
|
|
1189
|
+
.model({
|
|
1190
|
+
id: a.id().required(),
|
|
1191
|
+
subject: a.json().required(),
|
|
1192
|
+
mode: a.string().required(),
|
|
1193
|
+
reason: a.string().required(),
|
|
1194
|
+
ask: a.string(),
|
|
1195
|
+
raisedBy: a.string().required(),
|
|
1196
|
+
raisedAt: a.datetime().required(),
|
|
1197
|
+
deadline: a.datetime(),
|
|
1198
|
+
state: a.string().required(),
|
|
1199
|
+
answer: a.json(),
|
|
1200
|
+
links: a.json(),
|
|
1201
|
+
history: a.json(),
|
|
1202
|
+
appliedIn: a.json(),
|
|
1203
|
+
updatedAt: a.datetime().required(),
|
|
1204
|
+
})
|
|
1205
|
+
.authorization((allow) => [
|
|
1206
|
+
allow.group("editor").to(["read", "create", "update"]),
|
|
1207
|
+
allow.group(adminGroup).to(authoringOperations),
|
|
1208
|
+
]),
|
|
1209
|
+
/** One user's read mark on one review request; id is "<userKey>#<requestId>". */
|
|
1210
|
+
ReviewReadMark: a
|
|
1211
|
+
.model({
|
|
1212
|
+
id: a.id().required(),
|
|
1213
|
+
requestId: a.string().required(),
|
|
1214
|
+
userKey: a.string().required(),
|
|
1215
|
+
readAt: a.datetime().required(),
|
|
1216
|
+
})
|
|
1217
|
+
.authorization((allow) => [
|
|
1218
|
+
allow.groups(["editor", adminGroup]).to(["read", "create", "update"]),
|
|
1219
|
+
]),
|
|
1220
|
+
/** Who is told about review requests: scope default, mode or automation. Changed by data, not code. */
|
|
1221
|
+
ReviewRecipientRule: a
|
|
1222
|
+
.model({
|
|
1223
|
+
id: a.id().required(),
|
|
1224
|
+
scope: a.string().required(),
|
|
1225
|
+
key: a.string(),
|
|
1226
|
+
roles: a.string().array(),
|
|
1227
|
+
emails: a.string().array(),
|
|
1228
|
+
updatedBy: a.string(),
|
|
1229
|
+
updatedAt: a.datetime().required(),
|
|
1230
|
+
})
|
|
1231
|
+
.authorization((allow) => [
|
|
1232
|
+
allow.group("editor").to(["read"]),
|
|
1233
|
+
allow.group(adminGroup).to(authoringOperations),
|
|
1234
|
+
]),
|
|
1166
1235
|
NewsroomSection: a
|
|
1167
1236
|
.model({
|
|
1168
1237
|
id: a.id().required(),
|
package/amplify/data/resource.ts
CHANGED
|
@@ -1247,6 +1247,8 @@ const schema = a.schema({
|
|
|
1247
1247
|
error: a.json(),
|
|
1248
1248
|
attempt: a.integer(),
|
|
1249
1249
|
newsroomFeedKey: a.string(),
|
|
1250
|
+
usage: a.json(),
|
|
1251
|
+
costUsd: a.float(),
|
|
1250
1252
|
})
|
|
1251
1253
|
.secondaryIndexes((index) => [
|
|
1252
1254
|
index("procedureId").sortKeys(["requestedAt"]).queryField("listProcedureRunsByProcedureAndRequestedAt"),
|
|
@@ -1259,6 +1261,77 @@ const schema = a.schema({
|
|
|
1259
1261
|
allow.group(adminGroup).to(authoringOperations),
|
|
1260
1262
|
]),
|
|
1261
1263
|
|
|
1264
|
+
Automation: a
|
|
1265
|
+
.model({
|
|
1266
|
+
id: a.id().required(),
|
|
1267
|
+
title: a.string().required(),
|
|
1268
|
+
description: a.string(),
|
|
1269
|
+
enabled: a.boolean().required(),
|
|
1270
|
+
trigger: a.json().required(),
|
|
1271
|
+
procedureKey: a.string().required(),
|
|
1272
|
+
inputTemplate: a.json(),
|
|
1273
|
+
limits: a.json(),
|
|
1274
|
+
createdBy: a.string(),
|
|
1275
|
+
createdAt: a.datetime().required(),
|
|
1276
|
+
updatedBy: a.string(),
|
|
1277
|
+
updatedAt: a.datetime().required(),
|
|
1278
|
+
})
|
|
1279
|
+
.authorization((allow) => [
|
|
1280
|
+
allow.group("editor").to(["read", "create", "update"]),
|
|
1281
|
+
allow.group(adminGroup).to(authoringOperations),
|
|
1282
|
+
]),
|
|
1283
|
+
|
|
1284
|
+
/** A human review point raised by a step on any item (PPY-ab6a50). Review is not a story state. */
|
|
1285
|
+
ReviewRequest: a
|
|
1286
|
+
.model({
|
|
1287
|
+
id: a.id().required(),
|
|
1288
|
+
subject: a.json().required(),
|
|
1289
|
+
mode: a.string().required(),
|
|
1290
|
+
reason: a.string().required(),
|
|
1291
|
+
ask: a.string(),
|
|
1292
|
+
raisedBy: a.string().required(),
|
|
1293
|
+
raisedAt: a.datetime().required(),
|
|
1294
|
+
deadline: a.datetime(),
|
|
1295
|
+
state: a.string().required(),
|
|
1296
|
+
answer: a.json(),
|
|
1297
|
+
links: a.json(),
|
|
1298
|
+
history: a.json(),
|
|
1299
|
+
appliedIn: a.json(),
|
|
1300
|
+
updatedAt: a.datetime().required(),
|
|
1301
|
+
})
|
|
1302
|
+
.authorization((allow) => [
|
|
1303
|
+
allow.group("editor").to(["read", "create", "update"]),
|
|
1304
|
+
allow.group(adminGroup).to(authoringOperations),
|
|
1305
|
+
]),
|
|
1306
|
+
|
|
1307
|
+
/** One user's read mark on one review request; id is "<userKey>#<requestId>". */
|
|
1308
|
+
ReviewReadMark: a
|
|
1309
|
+
.model({
|
|
1310
|
+
id: a.id().required(),
|
|
1311
|
+
requestId: a.string().required(),
|
|
1312
|
+
userKey: a.string().required(),
|
|
1313
|
+
readAt: a.datetime().required(),
|
|
1314
|
+
})
|
|
1315
|
+
.authorization((allow) => [
|
|
1316
|
+
allow.groups(["editor", adminGroup]).to(["read", "create", "update"]),
|
|
1317
|
+
]),
|
|
1318
|
+
|
|
1319
|
+
/** Who is told about review requests: scope default, mode or automation. Changed by data, not code. */
|
|
1320
|
+
ReviewRecipientRule: a
|
|
1321
|
+
.model({
|
|
1322
|
+
id: a.id().required(),
|
|
1323
|
+
scope: a.string().required(),
|
|
1324
|
+
key: a.string(),
|
|
1325
|
+
roles: a.string().array(),
|
|
1326
|
+
emails: a.string().array(),
|
|
1327
|
+
updatedBy: a.string(),
|
|
1328
|
+
updatedAt: a.datetime().required(),
|
|
1329
|
+
})
|
|
1330
|
+
.authorization((allow) => [
|
|
1331
|
+
allow.group("editor").to(["read"]),
|
|
1332
|
+
allow.group(adminGroup).to(authoringOperations),
|
|
1333
|
+
]),
|
|
1334
|
+
|
|
1262
1335
|
NewsroomSection: a
|
|
1263
1336
|
.model({
|
|
1264
1337
|
id: a.id().required(),
|
package/amplify/data/schema.js
CHANGED
|
@@ -2,6 +2,7 @@ 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
4
|
import { editorialChecksSchemaDefinitions } from "../functions/editorial-checks/schema.js";
|
|
5
|
+
import { automationViewSchemaDefinitions } from "../functions/automation-view/schema.js";
|
|
5
6
|
import { monitoringViewSchemaDefinitions } from "../functions/monitoring-view/schema.js";
|
|
6
7
|
import { contentActions } from "../functions/content-actions/resource.js";
|
|
7
8
|
import { knowledgeQuery } from "../functions/knowledge-query/resource.js";
|
|
@@ -1071,6 +1072,8 @@ function buildBaseDefinitions(includeEditions) {
|
|
|
1071
1072
|
error: a.json(),
|
|
1072
1073
|
attempt: a.integer(),
|
|
1073
1074
|
newsroomFeedKey: a.string(),
|
|
1075
|
+
usage: a.json(),
|
|
1076
|
+
costUsd: a.float(),
|
|
1074
1077
|
})
|
|
1075
1078
|
.secondaryIndexes((index) => [
|
|
1076
1079
|
index("procedureId").sortKeys(["requestedAt"]).queryField("listProcedureRunsByProcedureAndRequestedAt"),
|
|
@@ -1082,6 +1085,70 @@ function buildBaseDefinitions(includeEditions) {
|
|
|
1082
1085
|
allow.group("editor").to(["read"]),
|
|
1083
1086
|
allow.group(adminGroup).to(authoringOperations),
|
|
1084
1087
|
]),
|
|
1088
|
+
/** A named "when X happens, run procedure Y" (PPY-9ff95b). Step 1 mirrors the monitoring router's procedureTriggers and starts nothing itself. */
|
|
1089
|
+
Automation: modelWithoutSubscriptions({
|
|
1090
|
+
id: a.id().required(),
|
|
1091
|
+
title: a.string().required(),
|
|
1092
|
+
description: a.string(),
|
|
1093
|
+
enabled: a.boolean().required(),
|
|
1094
|
+
trigger: a.json().required(),
|
|
1095
|
+
procedureKey: a.string().required(),
|
|
1096
|
+
inputTemplate: a.json(),
|
|
1097
|
+
limits: a.json(),
|
|
1098
|
+
createdBy: a.string(),
|
|
1099
|
+
createdAt: a.datetime().required(),
|
|
1100
|
+
updatedBy: a.string(),
|
|
1101
|
+
updatedAt: a.datetime().required(),
|
|
1102
|
+
})
|
|
1103
|
+
.authorization((allow) => [
|
|
1104
|
+
allow.group("editor").to(["read", "create", "update"]),
|
|
1105
|
+
allow.group(adminGroup).to(authoringOperations),
|
|
1106
|
+
]),
|
|
1107
|
+
/** A human review point raised by a step on any item (PPY-ab6a50). Review is not a story state. */
|
|
1108
|
+
ReviewRequest: modelWithoutSubscriptions({
|
|
1109
|
+
id: a.id().required(),
|
|
1110
|
+
subject: a.json().required(),
|
|
1111
|
+
mode: a.string().required(),
|
|
1112
|
+
reason: a.string().required(),
|
|
1113
|
+
ask: a.string(),
|
|
1114
|
+
raisedBy: a.string().required(),
|
|
1115
|
+
raisedAt: a.datetime().required(),
|
|
1116
|
+
deadline: a.datetime(),
|
|
1117
|
+
state: a.string().required(),
|
|
1118
|
+
answer: a.json(),
|
|
1119
|
+
links: a.json(),
|
|
1120
|
+
history: a.json(),
|
|
1121
|
+
appliedIn: a.json(),
|
|
1122
|
+
updatedAt: a.datetime().required(),
|
|
1123
|
+
})
|
|
1124
|
+
.authorization((allow) => [
|
|
1125
|
+
allow.group("editor").to(["read", "create", "update"]),
|
|
1126
|
+
allow.group(adminGroup).to(authoringOperations),
|
|
1127
|
+
]),
|
|
1128
|
+
/** One user's read mark on one review request; id is "<userKey>#<requestId>". */
|
|
1129
|
+
ReviewReadMark: modelWithoutSubscriptions({
|
|
1130
|
+
id: a.id().required(),
|
|
1131
|
+
requestId: a.string().required(),
|
|
1132
|
+
userKey: a.string().required(),
|
|
1133
|
+
readAt: a.datetime().required(),
|
|
1134
|
+
})
|
|
1135
|
+
.authorization((allow) => [
|
|
1136
|
+
allow.groups(["editor", adminGroup]).to(["read", "create", "update"]),
|
|
1137
|
+
]),
|
|
1138
|
+
/** Who is told about review requests: scope default, mode or automation. Changed by data, not code. */
|
|
1139
|
+
ReviewRecipientRule: modelWithoutSubscriptions({
|
|
1140
|
+
id: a.id().required(),
|
|
1141
|
+
scope: a.string().required(),
|
|
1142
|
+
key: a.string(),
|
|
1143
|
+
roles: a.string().array(),
|
|
1144
|
+
emails: a.string().array(),
|
|
1145
|
+
updatedBy: a.string(),
|
|
1146
|
+
updatedAt: a.datetime().required(),
|
|
1147
|
+
})
|
|
1148
|
+
.authorization((allow) => [
|
|
1149
|
+
allow.group("editor").to(["read"]),
|
|
1150
|
+
allow.group(adminGroup).to(authoringOperations),
|
|
1151
|
+
]),
|
|
1085
1152
|
NewsroomSection: modelWithoutSubscriptions({
|
|
1086
1153
|
id: a.id().required(),
|
|
1087
1154
|
tenantId: a.string(),
|
|
@@ -2021,6 +2088,7 @@ function buildBaseDefinitions(includeEditions) {
|
|
|
2021
2088
|
]),
|
|
2022
2089
|
...editorialChecksSchemaDefinitions,
|
|
2023
2090
|
...monitoringViewSchemaDefinitions,
|
|
2091
|
+
...automationViewSchemaDefinitions,
|
|
2024
2092
|
};
|
|
2025
2093
|
if (!includeEditions) {
|
|
2026
2094
|
const mutableDefinitions = definitions;
|
package/amplify/data/schema.ts
CHANGED
|
@@ -2,6 +2,7 @@ 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
4
|
import { editorialChecksSchemaDefinitions } from "../functions/editorial-checks/schema";
|
|
5
|
+
import { automationViewSchemaDefinitions } from "../functions/automation-view/schema";
|
|
5
6
|
import { monitoringViewSchemaDefinitions } from "../functions/monitoring-view/schema";
|
|
6
7
|
import { contentActions } from "../functions/content-actions/resource";
|
|
7
8
|
import { knowledgeQuery } from "../functions/knowledge-query/resource";
|
|
@@ -1164,6 +1165,8 @@ const definitions = {
|
|
|
1164
1165
|
error: a.json(),
|
|
1165
1166
|
attempt: a.integer(),
|
|
1166
1167
|
newsroomFeedKey: a.string(),
|
|
1168
|
+
usage: a.json(),
|
|
1169
|
+
costUsd: a.float(),
|
|
1167
1170
|
})
|
|
1168
1171
|
.secondaryIndexes((index) => [
|
|
1169
1172
|
index("procedureId").sortKeys(["requestedAt"]).queryField("listProcedureRunsByProcedureAndRequestedAt"),
|
|
@@ -1176,6 +1179,74 @@ const definitions = {
|
|
|
1176
1179
|
allow.group(adminGroup).to(authoringOperations),
|
|
1177
1180
|
]),
|
|
1178
1181
|
|
|
1182
|
+
/** A named "when X happens, run procedure Y" (PPY-9ff95b). Step 1 mirrors the monitoring router's procedureTriggers and starts nothing itself. */
|
|
1183
|
+
Automation: modelWithoutSubscriptions({
|
|
1184
|
+
id: a.id().required(),
|
|
1185
|
+
title: a.string().required(),
|
|
1186
|
+
description: a.string(),
|
|
1187
|
+
enabled: a.boolean().required(),
|
|
1188
|
+
trigger: a.json().required(),
|
|
1189
|
+
procedureKey: a.string().required(),
|
|
1190
|
+
inputTemplate: a.json(),
|
|
1191
|
+
limits: a.json(),
|
|
1192
|
+
createdBy: a.string(),
|
|
1193
|
+
createdAt: a.datetime().required(),
|
|
1194
|
+
updatedBy: a.string(),
|
|
1195
|
+
updatedAt: a.datetime().required(),
|
|
1196
|
+
})
|
|
1197
|
+
.authorization((allow) => [
|
|
1198
|
+
allow.group("editor").to(["read", "create", "update"]),
|
|
1199
|
+
allow.group(adminGroup).to(authoringOperations),
|
|
1200
|
+
]),
|
|
1201
|
+
|
|
1202
|
+
/** A human review point raised by a step on any item (PPY-ab6a50). Review is not a story state. */
|
|
1203
|
+
ReviewRequest: modelWithoutSubscriptions({
|
|
1204
|
+
id: a.id().required(),
|
|
1205
|
+
subject: a.json().required(),
|
|
1206
|
+
mode: a.string().required(),
|
|
1207
|
+
reason: a.string().required(),
|
|
1208
|
+
ask: a.string(),
|
|
1209
|
+
raisedBy: a.string().required(),
|
|
1210
|
+
raisedAt: a.datetime().required(),
|
|
1211
|
+
deadline: a.datetime(),
|
|
1212
|
+
state: a.string().required(),
|
|
1213
|
+
answer: a.json(),
|
|
1214
|
+
links: a.json(),
|
|
1215
|
+
history: a.json(),
|
|
1216
|
+
appliedIn: a.json(),
|
|
1217
|
+
updatedAt: a.datetime().required(),
|
|
1218
|
+
})
|
|
1219
|
+
.authorization((allow) => [
|
|
1220
|
+
allow.group("editor").to(["read", "create", "update"]),
|
|
1221
|
+
allow.group(adminGroup).to(authoringOperations),
|
|
1222
|
+
]),
|
|
1223
|
+
|
|
1224
|
+
/** One user's read mark on one review request; id is "<userKey>#<requestId>". */
|
|
1225
|
+
ReviewReadMark: modelWithoutSubscriptions({
|
|
1226
|
+
id: a.id().required(),
|
|
1227
|
+
requestId: a.string().required(),
|
|
1228
|
+
userKey: a.string().required(),
|
|
1229
|
+
readAt: a.datetime().required(),
|
|
1230
|
+
})
|
|
1231
|
+
.authorization((allow) => [
|
|
1232
|
+
allow.groups(["editor", adminGroup]).to(["read", "create", "update"]),
|
|
1233
|
+
]),
|
|
1234
|
+
|
|
1235
|
+
/** Who is told about review requests: scope default, mode or automation. Changed by data, not code. */
|
|
1236
|
+
ReviewRecipientRule: modelWithoutSubscriptions({
|
|
1237
|
+
id: a.id().required(),
|
|
1238
|
+
scope: a.string().required(),
|
|
1239
|
+
key: a.string(),
|
|
1240
|
+
roles: a.string().array(),
|
|
1241
|
+
emails: a.string().array(),
|
|
1242
|
+
updatedBy: a.string(),
|
|
1243
|
+
updatedAt: a.datetime().required(),
|
|
1244
|
+
})
|
|
1245
|
+
.authorization((allow) => [
|
|
1246
|
+
allow.group("editor").to(["read"]),
|
|
1247
|
+
allow.group(adminGroup).to(authoringOperations),
|
|
1248
|
+
]),
|
|
1249
|
+
|
|
1179
1250
|
NewsroomSection: modelWithoutSubscriptions({
|
|
1180
1251
|
id: a.id().required(),
|
|
1181
1252
|
tenantId: a.string(),
|
|
@@ -2145,6 +2216,7 @@ const definitions = {
|
|
|
2145
2216
|
]),
|
|
2146
2217
|
...editorialChecksSchemaDefinitions,
|
|
2147
2218
|
...monitoringViewSchemaDefinitions,
|
|
2219
|
+
...automationViewSchemaDefinitions,
|
|
2148
2220
|
};
|
|
2149
2221
|
if (!includeEditions) {
|
|
2150
2222
|
const mutableDefinitions = definitions as Record<string, unknown>;
|
|
@@ -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,25 @@
|
|
|
1
|
+
import { a } from "@aws-amplify/backend";
|
|
2
|
+
import { automationView } from "./resource.js";
|
|
3
|
+
const editors = ["editor", "admin"];
|
|
4
|
+
/**
|
|
5
|
+
* Core to every site's data API. The Automation page reads the automation records, the monitoring
|
|
6
|
+
* trigger rows they link to, and the procedure runs through `automationOverview`, and starts a run
|
|
7
|
+
* through `runAutomation`. Both admit editors and admins. AppSync also lets an IAM principal call the
|
|
8
|
+
* query when its own policy allows the field (every operation in this schema accepts IAM callers for
|
|
9
|
+
* the functions that call it); the function reads only for an IAM principal of its own account and refuses
|
|
10
|
+
* every IAM caller on `runAutomation`.
|
|
11
|
+
*/
|
|
12
|
+
export const automationViewSchemaDefinitions = {
|
|
13
|
+
automationOverview: a
|
|
14
|
+
.query()
|
|
15
|
+
.arguments({ automationId: a.string() })
|
|
16
|
+
.returns(a.json())
|
|
17
|
+
.authorization((allow) => [allow.groups(editors)])
|
|
18
|
+
.handler(a.handler.function(automationView)),
|
|
19
|
+
runAutomation: a
|
|
20
|
+
.mutation()
|
|
21
|
+
.arguments({ automationId: a.string().required(), input: a.json() })
|
|
22
|
+
.returns(a.json())
|
|
23
|
+
.authorization((allow) => [allow.groups(editors)])
|
|
24
|
+
.handler(a.handler.function(automationView)),
|
|
25
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { a } from "@aws-amplify/backend";
|
|
2
|
+
import { automationView } from "./resource";
|
|
3
|
+
|
|
4
|
+
const editors = ["editor", "admin"];
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Core to every site's data API. The Automation page reads the automation records, the monitoring
|
|
8
|
+
* trigger rows they link to, and the procedure runs through `automationOverview`, and starts a run
|
|
9
|
+
* through `runAutomation`. Both admit editors and admins. AppSync also lets an IAM principal call the
|
|
10
|
+
* query when its own policy allows the field (every operation in this schema accepts IAM callers for
|
|
11
|
+
* the functions that call it); the function reads only for an IAM principal of its own account and refuses
|
|
12
|
+
* every IAM caller on `runAutomation`.
|
|
13
|
+
*/
|
|
14
|
+
export const automationViewSchemaDefinitions = {
|
|
15
|
+
automationOverview: a
|
|
16
|
+
.query()
|
|
17
|
+
.arguments({ automationId: a.string() })
|
|
18
|
+
.returns(a.json())
|
|
19
|
+
.authorization((allow) => [allow.groups(editors)])
|
|
20
|
+
.handler(a.handler.function(automationView)),
|
|
21
|
+
runAutomation: a
|
|
22
|
+
.mutation()
|
|
23
|
+
.arguments({ automationId: a.string().required(), input: a.json() })
|
|
24
|
+
.returns(a.json())
|
|
25
|
+
.authorization((allow) => [allow.groups(editors)])
|
|
26
|
+
.handler(a.handler.function(automationView)),
|
|
27
|
+
};
|
|
@@ -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
|
+
}
|
|
@@ -7,7 +7,7 @@ export const AI_MODEL_NAME = /^[a-z0-9][a-z0-9.-]{0,63}$/;
|
|
|
7
7
|
export const SUBMISSION_KIND_NAME = /^[a-z][a-z0-9-]{0,23}$/;
|
|
8
8
|
export const SUBJECT_TYPES = ["item", "submission", "story", "proposal", "concept"];
|
|
9
9
|
export const COMPUTE_ROLE_ARN = /^arn:aws:iam::[0-9]{12}:role\/[A-Za-z0-9+=,.@_/-]{1,512}$/;
|
|
10
|
-
const KNOWN_KEYS = new Set(["name", "computeRoleArn", "dimensions", "limits", "reservedHandles", "karma", "ranking", "snapshot", "retirementPoll", "reservedConcurrency", "hypeGap", "submissions", "comments", "moderation", "subjects"]);
|
|
10
|
+
const KNOWN_KEYS = new Set(["name", "computeRoleArn", "dimensions", "limits", "reservedHandles", "karma", "ranking", "snapshot", "retirementPoll", "reservedConcurrency", "hypeGap", "submissions", "comments", "moderation", "subjects", "proposals"]);
|
|
11
11
|
function object(raw, label, known) {
|
|
12
12
|
if (raw === undefined)
|
|
13
13
|
return {};
|
|
@@ -117,6 +117,7 @@ export function resolveReaderSteeringConfig(request) {
|
|
|
117
117
|
if (!Array.isArray(subjectTypes) || subjectTypes.length === 0 || !subjectTypes.every((type) => SUBJECT_TYPES.includes(type))) {
|
|
118
118
|
throw new Error(`reader-steering plugin subjects.types must be a non-empty list of: ${SUBJECT_TYPES.join(", ")}`);
|
|
119
119
|
}
|
|
120
|
+
const proposals = object(raw.proposals, "proposals", ["publish"]);
|
|
120
121
|
const kinds = names(submissions.kinds, "submissions.kinds", SUBMISSION_KIND_NAME);
|
|
121
122
|
const comments = object(raw.comments, "comments", ["perHour", "editWindowMinutes", "maximumLength", "maximumLinks"]);
|
|
122
123
|
const hypeGap = resolveHypeGap(raw.hypeGap, dimensions);
|
|
@@ -173,6 +174,7 @@ export function resolveReaderSteeringConfig(request) {
|
|
|
173
174
|
maximumLength: integer(comments.maximumLength, "comments.maximumLength", 2000, 1, 10000),
|
|
174
175
|
maximumLinks: integer(comments.maximumLinks, "comments.maximumLinks", 3, 0, 50),
|
|
175
176
|
},
|
|
177
|
+
proposals: { publish: flag(proposals.publish, "proposals.publish") },
|
|
176
178
|
retirementPoll,
|
|
177
179
|
...(hypeGap ? { hypeGap } : {}),
|
|
178
180
|
...(raw.reservedConcurrency === undefined ? {} : { reservedConcurrency: integer(raw.reservedConcurrency, "reservedConcurrency", 1, 1, 1000) }),
|