@anthusai/papyrus 3.0.0 → 3.2.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 +1 -0
- package/amplify/data/resource.ts +1 -0
- package/amplify/data/schema.js +2 -0
- package/amplify/data/schema.ts +2 -0
- package/amplify/functions/assignment-action/data-access.js +88 -0
- package/amplify/functions/assignment-action/data-access.ts +87 -0
- package/amplify/plugins/editorial-checks/function/handler.py +4 -1
- package/amplify/plugins/editorial-checks/papyrus.plugin.js +23 -2
- package/amplify/plugins/editorial-checks/papyrus.plugin.ts +26 -2
- package/amplify/site-backend.js +2 -0
- package/amplify/site-backend.ts +5 -0
- package/components/newsroom-board-view.tsx +4 -1
- package/lib/newsroom-board.ts +27 -2
- package/package.json +2 -2
- package/routes.manifest.json +1 -1
package/amplify/data/resource.js
CHANGED
|
@@ -2146,6 +2146,7 @@ const schema = a.schema({
|
|
|
2146
2146
|
allow.groups(contentWriteGroups),
|
|
2147
2147
|
]),
|
|
2148
2148
|
}).authorization((allow) => [
|
|
2149
|
+
allow.resource(assignmentAction).to(["query", "mutate"]),
|
|
2149
2150
|
allow.resource(categoryAction),
|
|
2150
2151
|
allow.resource(contentActions),
|
|
2151
2152
|
allow.resource(manageUserRole),
|
package/amplify/data/resource.ts
CHANGED
|
@@ -2271,6 +2271,7 @@ const schema = a.schema({
|
|
|
2271
2271
|
allow.groups(contentWriteGroups),
|
|
2272
2272
|
]),
|
|
2273
2273
|
}).authorization((allow) => [
|
|
2274
|
+
allow.resource(assignmentAction).to(["query", "mutate"]),
|
|
2274
2275
|
allow.resource(categoryAction),
|
|
2275
2276
|
allow.resource(contentActions),
|
|
2276
2277
|
allow.resource(manageUserRole),
|
package/amplify/data/schema.js
CHANGED
|
@@ -1989,6 +1989,7 @@ const baseDefinitions = {
|
|
|
1989
1989
|
]),
|
|
1990
1990
|
};
|
|
1991
1991
|
export const schema = a.schema(baseDefinitions).authorization((allow) => [
|
|
1992
|
+
allow.resource(assignmentAction).to(["query", "mutate"]),
|
|
1992
1993
|
allow.resource(categoryAction),
|
|
1993
1994
|
allow.resource(contentActions),
|
|
1994
1995
|
allow.resource(manageUserRole),
|
|
@@ -1999,6 +2000,7 @@ export const schema = a.schema(baseDefinitions).authorization((allow) => [
|
|
|
1999
2000
|
/** The deployed schema of a site that enables plugins: the base definitions plus what each plugin contributes (docs/plugins.md). */
|
|
2000
2001
|
export function schemaWithPlugins(pluginDefinitions) {
|
|
2001
2002
|
return a.schema({ ...baseDefinitions, ...pluginDefinitions }).authorization((allow) => [
|
|
2003
|
+
allow.resource(assignmentAction).to(["query", "mutate"]),
|
|
2002
2004
|
allow.resource(categoryAction),
|
|
2003
2005
|
allow.resource(contentActions),
|
|
2004
2006
|
allow.resource(manageUserRole),
|
package/amplify/data/schema.ts
CHANGED
|
@@ -2108,6 +2108,7 @@ const baseDefinitions = {
|
|
|
2108
2108
|
};
|
|
2109
2109
|
|
|
2110
2110
|
export const schema = a.schema(baseDefinitions).authorization((allow) => [
|
|
2111
|
+
allow.resource(assignmentAction).to(["query", "mutate"]),
|
|
2111
2112
|
allow.resource(categoryAction),
|
|
2112
2113
|
allow.resource(contentActions),
|
|
2113
2114
|
allow.resource(manageUserRole),
|
|
@@ -2119,6 +2120,7 @@ export const schema = a.schema(baseDefinitions).authorization((allow) => [
|
|
|
2119
2120
|
/** The deployed schema of a site that enables plugins: the base definitions plus what each plugin contributes (docs/plugins.md). */
|
|
2120
2121
|
export function schemaWithPlugins(pluginDefinitions: Record<string, any>) {
|
|
2121
2122
|
return a.schema({ ...baseDefinitions, ...pluginDefinitions } as typeof baseDefinitions).authorization((allow) => [
|
|
2123
|
+
allow.resource(assignmentAction).to(["query", "mutate"]),
|
|
2122
2124
|
allow.resource(categoryAction),
|
|
2123
2125
|
allow.resource(contentActions),
|
|
2124
2126
|
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
|
+
}
|
|
@@ -4,7 +4,10 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
|
-
from papyrus_content.editorial_checks import GraphQLEditorialStore, Services, handle
|
|
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()
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
def handler(event: dict[str, Any], _context: Any = None) -> dict[str, Any]:
|
|
@@ -7,12 +7,28 @@ export const EDITORIAL_CHECKS_QUERIES = ["checkEditorial", "suggestEditorialRewr
|
|
|
7
7
|
export const EDITORIAL_CHECKS_MUTATIONS = ["importEditorialStarterPack", "recalibrateEditorialPolicies"];
|
|
8
8
|
/** The model operations the function performs with its own role. */
|
|
9
9
|
export const EDITORIAL_CHECKS_FUNCTION_FIELDS = [
|
|
10
|
+
"Query/fields/listKnowledgeCorporaByRoleAndName",
|
|
11
|
+
"Query/fields/getEditorialContext",
|
|
10
12
|
"Query/fields/listEditorialPolicies",
|
|
11
13
|
"Query/fields/getEditorialCheckSettings",
|
|
12
14
|
"Query/fields/listEditorialFeedbacks",
|
|
13
15
|
"Mutation/fields/createEditorialPolicy",
|
|
14
16
|
"Mutation/fields/updateEditorialPolicy",
|
|
15
17
|
];
|
|
18
|
+
export const EDITORIAL_CHECKS_SECRETS = ["TYPESAFE_API_KEY", "OPENAI_API_KEY"];
|
|
19
|
+
/**
|
|
20
|
+
* SSM parameters holding the provider keys. A Python function reads its secret()
|
|
21
|
+
* values itself (Amplify only resolves them for Node functions), so it needs read
|
|
22
|
+
* access to the app's branch and shared secret paths, and to the sandbox paths.
|
|
23
|
+
*/
|
|
24
|
+
export function editorialChecksSecretParameters(region, account, appId) {
|
|
25
|
+
const arn = (path) => `arn:aws:ssm:${region}:${account}:parameter/amplify/${path}`;
|
|
26
|
+
return EDITORIAL_CHECKS_SECRETS.flatMap((name) => [
|
|
27
|
+
arn(`papyrus/*/${name}`),
|
|
28
|
+
arn(`shared/papyrus/${name}`),
|
|
29
|
+
...(appId ? [arn(`${appId}/*/${name}`), arn(`shared/${appId}/${name}`)] : []),
|
|
30
|
+
]);
|
|
31
|
+
}
|
|
16
32
|
export const editorialChecksManifest = {
|
|
17
33
|
name: "editorial-checks",
|
|
18
34
|
contract: 1,
|
|
@@ -39,8 +55,13 @@ export const editorialChecksPlugin = {
|
|
|
39
55
|
const lambda = backend.editorialChecks.resources.lambda;
|
|
40
56
|
const stack = Stack.of(lambda);
|
|
41
57
|
lambda.addEnvironment("PAPYRUS_GRAPHQL_ENDPOINT", context.graphqlEndpoint);
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
for (const name of EDITORIAL_CHECKS_SECRETS)
|
|
59
|
+
backend.editorialChecks.addEnvironment(name, secret(name));
|
|
60
|
+
lambda.addToRolePolicy(new PolicyStatement({
|
|
61
|
+
effect: Effect.ALLOW,
|
|
62
|
+
actions: ["ssm:GetParameter", "ssm:GetParameters"],
|
|
63
|
+
resources: editorialChecksSecretParameters(stack.region, stack.account, (process.env.AWS_APP_ID ?? "").trim()),
|
|
64
|
+
}));
|
|
44
65
|
lambda.addToRolePolicy(new PolicyStatement({
|
|
45
66
|
effect: Effect.ALLOW,
|
|
46
67
|
actions: ["appsync:GraphQL"],
|
|
@@ -14,6 +14,8 @@ export const EDITORIAL_CHECKS_MUTATIONS = ["importEditorialStarterPack", "recali
|
|
|
14
14
|
|
|
15
15
|
/** The model operations the function performs with its own role. */
|
|
16
16
|
export const EDITORIAL_CHECKS_FUNCTION_FIELDS = [
|
|
17
|
+
"Query/fields/listKnowledgeCorporaByRoleAndName",
|
|
18
|
+
"Query/fields/getEditorialContext",
|
|
17
19
|
"Query/fields/listEditorialPolicies",
|
|
18
20
|
"Query/fields/getEditorialCheckSettings",
|
|
19
21
|
"Query/fields/listEditorialFeedbacks",
|
|
@@ -21,6 +23,22 @@ export const EDITORIAL_CHECKS_FUNCTION_FIELDS = [
|
|
|
21
23
|
"Mutation/fields/updateEditorialPolicy",
|
|
22
24
|
];
|
|
23
25
|
|
|
26
|
+
export const EDITORIAL_CHECKS_SECRETS = ["TYPESAFE_API_KEY", "OPENAI_API_KEY"];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* SSM parameters holding the provider keys. A Python function reads its secret()
|
|
30
|
+
* values itself (Amplify only resolves them for Node functions), so it needs read
|
|
31
|
+
* access to the app's branch and shared secret paths, and to the sandbox paths.
|
|
32
|
+
*/
|
|
33
|
+
export function editorialChecksSecretParameters(region: string, account: string, appId: string): string[] {
|
|
34
|
+
const arn = (path: string) => `arn:aws:ssm:${region}:${account}:parameter/amplify/${path}`;
|
|
35
|
+
return EDITORIAL_CHECKS_SECRETS.flatMap((name) => [
|
|
36
|
+
arn(`papyrus/*/${name}`),
|
|
37
|
+
arn(`shared/papyrus/${name}`),
|
|
38
|
+
...(appId ? [arn(`${appId}/*/${name}`), arn(`shared/${appId}/${name}`)] : []),
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
41
|
+
|
|
24
42
|
export const editorialChecksManifest: PluginManifest = {
|
|
25
43
|
name: "editorial-checks",
|
|
26
44
|
contract: 1,
|
|
@@ -48,8 +66,14 @@ export const editorialChecksPlugin: PapyrusPlugin<EditorialChecksPluginConfig, {
|
|
|
48
66
|
const lambda = backend.editorialChecks.resources.lambda as LambdaFunction;
|
|
49
67
|
const stack = Stack.of(lambda);
|
|
50
68
|
lambda.addEnvironment("PAPYRUS_GRAPHQL_ENDPOINT", context.graphqlEndpoint);
|
|
51
|
-
backend.editorialChecks.addEnvironment(
|
|
52
|
-
|
|
69
|
+
for (const name of EDITORIAL_CHECKS_SECRETS) backend.editorialChecks.addEnvironment(name, secret(name));
|
|
70
|
+
lambda.addToRolePolicy(
|
|
71
|
+
new PolicyStatement({
|
|
72
|
+
effect: Effect.ALLOW,
|
|
73
|
+
actions: ["ssm:GetParameter", "ssm:GetParameters"],
|
|
74
|
+
resources: editorialChecksSecretParameters(stack.region, stack.account, (process.env.AWS_APP_ID ?? "").trim()),
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
53
77
|
lambda.addToRolePolicy(
|
|
54
78
|
new PolicyStatement({
|
|
55
79
|
effect: Effect.ALLOW,
|
package/amplify/site-backend.js
CHANGED
|
@@ -23,6 +23,7 @@ import { defineSiteData } from "./data/resource.js";
|
|
|
23
23
|
import { resolvePlugins } from "./plugins/registry.js";
|
|
24
24
|
import { wirePlugins } from "./plugins/wire.js";
|
|
25
25
|
import { assignmentAction } from "./functions/assignment-action/resource.js";
|
|
26
|
+
import { scopeAssignmentDataAccess } from "./functions/assignment-action/data-access.js";
|
|
26
27
|
import { categoryAction } from "./functions/category-action/resource.js";
|
|
27
28
|
import { ConsoleChatResponderStack } from "./functions/console-chat-responder/resource.js";
|
|
28
29
|
import { contentActions, setSiteFilesForContentActionsBundle } from "./functions/content-actions/resource.js";
|
|
@@ -304,6 +305,7 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
304
305
|
}
|
|
305
306
|
grantCorporaRead(backend.knowledgeQuery.resources.lambda);
|
|
306
307
|
grantNewsroomReadWrite(backend.assignmentAction.resources.lambda);
|
|
308
|
+
scopeAssignmentDataAccess(backend.assignmentAction.resources.lambda, backend.data.resources.graphqlApi.arn);
|
|
307
309
|
grantNewsroomReadWrite(backend.categoryAction.resources.lambda);
|
|
308
310
|
grantNewsroomReadWrite(backend.newsroomSummary.resources.lambda);
|
|
309
311
|
const contentActionsLambda = backend.contentActions.resources.lambda;
|
package/amplify/site-backend.ts
CHANGED
|
@@ -30,6 +30,7 @@ import { defineSiteData } from "./data/resource";
|
|
|
30
30
|
import { resolvePlugins } from "./plugins/registry";
|
|
31
31
|
import { wirePlugins } from "./plugins/wire";
|
|
32
32
|
import { assignmentAction } from "./functions/assignment-action/resource";
|
|
33
|
+
import { scopeAssignmentDataAccess } from "./functions/assignment-action/data-access";
|
|
33
34
|
import { categoryAction } from "./functions/category-action/resource";
|
|
34
35
|
import { ConsoleChatResponderStack } from "./functions/console-chat-responder/resource";
|
|
35
36
|
import { contentActions, setSiteFilesForContentActionsBundle } from "./functions/content-actions/resource";
|
|
@@ -387,6 +388,10 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
387
388
|
}
|
|
388
389
|
grantCorporaRead(backend.knowledgeQuery.resources.lambda as LambdaFunction);
|
|
389
390
|
grantNewsroomReadWrite(backend.assignmentAction.resources.lambda as LambdaFunction);
|
|
391
|
+
scopeAssignmentDataAccess(
|
|
392
|
+
backend.assignmentAction.resources.lambda as LambdaFunction,
|
|
393
|
+
backend.data.resources.graphqlApi.arn,
|
|
394
|
+
);
|
|
390
395
|
grantNewsroomReadWrite(backend.categoryAction.resources.lambda as LambdaFunction);
|
|
391
396
|
grantNewsroomReadWrite(backend.newsroomSummary.resources.lambda as LambdaFunction);
|
|
392
397
|
const contentActionsLambda = backend.contentActions.resources.lambda as LambdaFunction;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import Link from "next/link";
|
|
5
5
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
6
|
-
import { IssueCollection, IssueDetailPanel, IssueTypeFilter, issueTypeFromFilter, useBoardColumnState } from "@kanbus/ui";
|
|
6
|
+
import { IssueCollection, IssueDetailPanel, IssueIdFormatProvider, IssueTypeFilter, issueTypeFromFilter, useBoardColumnState } from "@kanbus/ui";
|
|
7
7
|
import "./newsroom-theme";
|
|
8
8
|
import { PlusIcon } from "lucide-react";
|
|
9
9
|
import { newsroomHref } from "../lib/newsroom-base-path";
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
deskBoardConfig,
|
|
17
17
|
deskContainsAssignment,
|
|
18
18
|
deskContainsContentItem,
|
|
19
|
+
formatNativeIssueId,
|
|
19
20
|
projectAssignmentBoard,
|
|
20
21
|
projectContentItemBoard,
|
|
21
22
|
type NativeBoardIssue,
|
|
@@ -168,6 +169,7 @@ export function NewsroomBoardView({ subject, deskId }: { subject: NewsroomBoardS
|
|
|
168
169
|
</Link>
|
|
169
170
|
)}
|
|
170
171
|
</>}>
|
|
172
|
+
<IssueIdFormatProvider formatIssueId={formatNativeIssueId}>
|
|
171
173
|
<div className="organizer-native-board">
|
|
172
174
|
{error && <NewsroomOpsStatusBanner tone="error">{error}</NewsroomOpsStatusBanner>}
|
|
173
175
|
{subject === "desk" && <IssueTypeFilter config={config} value={typeFilter} onChange={setTypeFilter} name="Record type" className="organizer-board-type-filter" />}
|
|
@@ -192,5 +194,6 @@ export function NewsroomBoardView({ subject, deskId }: { subject: NewsroomBoardS
|
|
|
192
194
|
</IssueDetailPanel>}
|
|
193
195
|
</div>
|
|
194
196
|
</div>
|
|
197
|
+
</IssueIdFormatProvider>
|
|
195
198
|
</NewsroomOpsShell>;
|
|
196
199
|
}
|
package/lib/newsroom-board.ts
CHANGED
|
@@ -21,6 +21,31 @@ export interface NativeBoardIssue extends KanbanIssue {
|
|
|
21
21
|
href: string;
|
|
22
22
|
/** A few lines to show under the headline: the blurb, else the subtitle. */
|
|
23
23
|
excerpt?: string;
|
|
24
|
+
/** The quiet path shown where a board would print the ID, e.g. `articles/my-slug` or `assignments/write-it`. */
|
|
25
|
+
displayId: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function slugify(text: string): string {
|
|
29
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Section and slug for a content item, falling back to the publication's board path segment. */
|
|
33
|
+
export function contentItemDisplayId(version: { section?: string | null; slug?: string | null; lineageId: string }): string {
|
|
34
|
+
const section = slugify(version.section ?? "") || contentItemBoardPathSegment();
|
|
35
|
+
return `${section}/${slugify(version.slug ?? "") || version.lineageId.slice(0, 8)}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Assignments have no slug: use the title as one, else a short id. */
|
|
39
|
+
export function assignmentDisplayId(assignment: { id: string; title?: string | null }): string {
|
|
40
|
+
const titleSlug = slugify(assignment.title ?? "").slice(0, 48).replace(/-+$/g, "");
|
|
41
|
+
return `assignments/${titleSlug || assignment.id.slice(0, 8)}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** `formatIssueId` for the Kanbus UI: the host-provided path, never the raw `kind:id` prefix. */
|
|
45
|
+
export function formatNativeIssueId(issue: KanbanIssue): string {
|
|
46
|
+
const displayId = (issue as Partial<NativeBoardIssue>).displayId;
|
|
47
|
+
if (displayId) return displayId;
|
|
48
|
+
return issue.id.replace(/^[A-Za-z]+:/, "").slice(0, 12);
|
|
24
49
|
}
|
|
25
50
|
|
|
26
51
|
export function contentItemBoardPathSegment(): string {
|
|
@@ -60,7 +85,7 @@ export function projectContentItemBoard(records: {
|
|
|
60
85
|
title: version.headline || version.title || version.slug, nativeStatus: stage, status: `${contentItemType}/${stage}`,
|
|
61
86
|
priority: 2, description: workCount ? `${workCount} assignment${workCount === 1 ? "" : "s"} on this ${contentItemType}.` : undefined,
|
|
62
87
|
updated_at: version.updatedAt ?? undefined, href: `/newsroom/articles/${encodeURIComponent(version.id)}`,
|
|
63
|
-
excerpt: contentItemExcerpt(version),
|
|
88
|
+
excerpt: contentItemExcerpt(version), displayId: contentItemDisplayId(version),
|
|
64
89
|
};
|
|
65
90
|
});
|
|
66
91
|
}
|
|
@@ -73,7 +98,7 @@ export function projectAssignmentBoard(assignments: readonly AssignmentRecord[])
|
|
|
73
98
|
priority: row.priority ?? 2, assignee: row.assigneeId || undefined,
|
|
74
99
|
description: row.summary || row.brief || undefined,
|
|
75
100
|
created_at: row.createdAt, updated_at: row.updatedAt,
|
|
76
|
-
href: `/newsroom/assignments/${encodeURIComponent(row.id)}`,
|
|
101
|
+
href: `/newsroom/assignments/${encodeURIComponent(row.id)}`, displayId: assignmentDisplayId(row),
|
|
77
102
|
}));
|
|
78
103
|
}
|
|
79
104
|
|
package/package.json
CHANGED
|
@@ -395,7 +395,7 @@
|
|
|
395
395
|
"optional": true
|
|
396
396
|
}
|
|
397
397
|
},
|
|
398
|
-
"version": "3.
|
|
398
|
+
"version": "3.2.0",
|
|
399
399
|
"dependencies": {
|
|
400
400
|
"@aws-amplify/adapter-nextjs": "^1.7.3",
|
|
401
401
|
"@aws-crypto/sha256-js": "5.2.0",
|
|
@@ -422,7 +422,7 @@
|
|
|
422
422
|
"@fontsource-variable/source-serif-4": "5.3.1",
|
|
423
423
|
"@fontsource/ibm-plex-mono": "5.3.0",
|
|
424
424
|
"@fontsource/ibm-plex-sans": "5.3.0",
|
|
425
|
-
"@kanbus/ui": "https://github.com/AnthusAI/Kanbus/releases/download/ui-v0.4.
|
|
425
|
+
"@kanbus/ui": "https://github.com/AnthusAI/Kanbus/releases/download/ui-v0.4.1/kanbus-ui-0.4.1.tgz",
|
|
426
426
|
"@lezer/highlight": "^1.2.5",
|
|
427
427
|
"@radix-ui/colors": "3.0.0",
|
|
428
428
|
"@smithy/protocol-http": "5.4.1",
|
package/routes.manifest.json
CHANGED