@anthusai/papyrus 1.11.0 → 1.13.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 +67 -0
- package/amplify/data/resource.ts +72 -0
- package/amplify/data/schema.js +67 -0
- package/amplify/data/schema.ts +72 -0
- package/amplify/functions/category-action/handler.ts +172 -2
- package/amplify/functions/content-actions/requirements.txt +1 -1
- package/amplify/functions/content-actions/resource.js +1 -1
- package/amplify/functions/content-actions/resource.ts +1 -1
- package/amplify/functions/renditions/requirements.txt +1 -1
- package/amplify/functions/shared/curation-delegation.ts +67 -0
- package/amplify/functions/shared/reference-curation-metadata.ts +20 -0
- package/amplify/publication/renditions-function.js +1 -1
- package/amplify/publication/renditions-function.ts +1 -1
- package/components/administration-delegations-panel.tsx +204 -0
- package/components/board-card-excerpts.tsx +43 -27
- package/components/curation-delegation-client.ts +81 -0
- package/components/news-desk-taxonomy-client.ts +7 -0
- package/components/newsroom-organizer.css +22 -0
- package/components/newsroom-references-view.tsx +56 -21
- package/components/prospectus-account-chooser.tsx +14 -19
- package/components/reference-cyclotron-status.tsx +5 -5
- package/components/reference-cyclotron-suggestion.tsx +143 -0
- package/components/reference-live-agreement.tsx +62 -0
- package/components/topic-steering-workspace.tsx +49 -3
- package/lib/content-source-context.ts +9 -1
- package/lib/curation-delegation-helpers.ts +59 -0
- package/lib/cyclotron-suggestions.ts +263 -0
- package/lib/graphql-content-repository.ts +42 -7
- package/lib/local-preview-seed.ts +141 -3
- package/lib/local-preview-tenant.ts +27 -0
- package/lib/prospectus-tenant.ts +22 -0
- package/lib/relevance-decisions.ts +29 -0
- package/lib/tenant-keys.ts +32 -0
- package/package.json +10 -1
- package/routes.manifest.json +1 -1
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import type { AssignmentRecord, ReferenceRecord } from "./category-repository";
|
|
1
|
+
import type { AssignmentRecord, MessageRecord, ReferenceRecord, SemanticRelationRecord } from "./category-repository";
|
|
2
|
+
import { SHADOW_AGREEMENT_SCHEMA, type ShadowAgreement } from "./cyclotron-suggestions";
|
|
2
3
|
import type { EditableArticle } from "./newsroom-articles";
|
|
3
4
|
import type { MediaAssetRecord } from "./newsroom-media";
|
|
4
5
|
import { EMPTY_STORY_REPORTING_TRAIL, type StoryReportingTrail, type TrailVersion } from "./story-reporting-trail";
|
|
5
6
|
import { contentItemStageKeys, type ContentItemVersionRecord } from "./content-item-lifecycle";
|
|
6
7
|
import { getContentItemStageDefinitions } from "./site-brand";
|
|
8
|
+
import { LOCAL_PREVIEW_TENANTS, getLocalPreviewTenantId } from "./local-preview-tenant";
|
|
7
9
|
|
|
8
10
|
const SEED_TIMESTAMP_BASE = Date.parse("2026-10-01T12:00:00.000Z");
|
|
9
11
|
const ONE_HOUR_IN_MILLISECONDS = 60 * 60 * 1000;
|
|
10
12
|
|
|
11
|
-
const
|
|
13
|
+
const SEED_CONTENT_ITEM_HEADLINES_BY_POSITION_FOR_TENANT_ZERO: readonly (readonly string[])[] = [
|
|
12
14
|
["Three sources confirm the zoning reversal", "Reporting packet: school board redistricting"],
|
|
13
15
|
["Draft in progress: the last ferry crossing", "Draft in progress: neighborhood bakery profile"],
|
|
14
16
|
["Illustration brief: flooded streets, aerial view", "Illustration brief: the market at dawn"],
|
|
@@ -18,6 +20,24 @@ const SEED_CONTENT_ITEM_HEADLINES_BY_POSITION: readonly (readonly string[])[] =
|
|
|
18
20
|
["Spiked: the unconfirmed council rumor", "Spiked: the recycled press release"],
|
|
19
21
|
];
|
|
20
22
|
|
|
23
|
+
const SEED_CONTENT_ITEM_HEADLINES_BY_POSITION_FOR_TENANT_ONE: readonly (readonly string[])[] = [
|
|
24
|
+
["Field note: spore dispersal on a damp morning", "Field note: the dung beetle census"],
|
|
25
|
+
["Draft in progress: why fungi throw their caps", "Draft in progress: the compost experiment"],
|
|
26
|
+
["Illustration brief: a cap in flight, close up", "Illustration brief: the mycelium network"],
|
|
27
|
+
["Layout pass: the monthly field guide", "Layout pass: the reader mailbag"],
|
|
28
|
+
["Published: a season of mushroom sightings", "Published: how the forest floor recycles"],
|
|
29
|
+
["Held for spring: the truffle hunt", "Evergreen: a beginner's guide to identification"],
|
|
30
|
+
["Spiked: the unverified foraging claim", "Spiked: the duplicate field report"],
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const SEED_CONTENT_ITEM_HEADLINES_BY_TENANT = [SEED_CONTENT_ITEM_HEADLINES_BY_POSITION_FOR_TENANT_ZERO, SEED_CONTENT_ITEM_HEADLINES_BY_POSITION_FOR_TENANT_ONE] as const;
|
|
34
|
+
|
|
35
|
+
function headlinesForSelectedTenant(): readonly (readonly string[])[] {
|
|
36
|
+
const tenantId = getLocalPreviewTenantId();
|
|
37
|
+
const tenant = LOCAL_PREVIEW_TENANTS.find(candidate => candidate.accountId === tenantId) ?? LOCAL_PREVIEW_TENANTS[0];
|
|
38
|
+
return SEED_CONTENT_ITEM_HEADLINES_BY_TENANT[tenant.fixtureHeadlineSet] ?? SEED_CONTENT_ITEM_HEADLINES_BY_TENANT[0];
|
|
39
|
+
}
|
|
40
|
+
|
|
21
41
|
function isoTimestampHoursBeforeBase(hours: number): string {
|
|
22
42
|
return new Date(SEED_TIMESTAMP_BASE - hours * ONE_HOUR_IN_MILLISECONDS).toISOString();
|
|
23
43
|
}
|
|
@@ -29,7 +49,8 @@ function slugFromHeadline(headline: string): string {
|
|
|
29
49
|
export function createLocalPreviewContentItemVersions(): ContentItemVersionRecord[] {
|
|
30
50
|
const stageKeys = contentItemStageKeys(getContentItemStageDefinitions());
|
|
31
51
|
return stageKeys.flatMap((stageKey, stageIndex) => {
|
|
32
|
-
const
|
|
52
|
+
const tenantHeadlines = headlinesForSelectedTenant();
|
|
53
|
+
const headlines = tenantHeadlines[stageIndex % tenantHeadlines.length];
|
|
33
54
|
return headlines.map((headline, headlineIndex) => {
|
|
34
55
|
const slug = slugFromHeadline(headline);
|
|
35
56
|
return {
|
|
@@ -146,3 +167,120 @@ export function createLocalPreviewReportingTrail(lineageId: string): StoryReport
|
|
|
146
167
|
export function createLocalPreviewMediaAssets(_itemId: string): MediaAssetRecord[] {
|
|
147
168
|
return [];
|
|
148
169
|
}
|
|
170
|
+
|
|
171
|
+
type PreviewSuggestionPlan = {
|
|
172
|
+
label: "include" | "exclude";
|
|
173
|
+
confidence: number;
|
|
174
|
+
curation: "none" | "human" | "service" | "delegated";
|
|
175
|
+
provenance: boolean;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const PREVIEW_SUGGESTION_PLANS: readonly PreviewSuggestionPlan[] = [
|
|
179
|
+
{ label: "include", confidence: 0.91, curation: "none", provenance: true },
|
|
180
|
+
{ label: "include", confidence: 0.86, curation: "human", provenance: true },
|
|
181
|
+
{ label: "exclude", confidence: 0.72, curation: "service", provenance: true },
|
|
182
|
+
{ label: "exclude", confidence: 0.88, curation: "delegated", provenance: true },
|
|
183
|
+
{ label: "exclude", confidence: 0.64, curation: "none", provenance: false },
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
const PREVIEW_CYCLOTRON_ID = "papyrus-relevance";
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Fixture data for reviewing the cyclotron suggestion UI without a backend:
|
|
190
|
+
* suggestions with provenance on the first references, a mix of human and
|
|
191
|
+
* non-human curation messages, and a live-agreement payload.
|
|
192
|
+
*/
|
|
193
|
+
export function createLocalPreviewCyclotronFixtures(references: ReferenceRecord[]): {
|
|
194
|
+
relations: SemanticRelationRecord[];
|
|
195
|
+
messages: MessageRecord[];
|
|
196
|
+
shadowAgreement: ShadowAgreement;
|
|
197
|
+
} {
|
|
198
|
+
const relations: SemanticRelationRecord[] = [];
|
|
199
|
+
const messages: MessageRecord[] = [];
|
|
200
|
+
references.slice(0, PREVIEW_SUGGESTION_PLANS.length).forEach((reference, index) => {
|
|
201
|
+
const plan = PREVIEW_SUGGESTION_PLANS[index];
|
|
202
|
+
const lineageId = reference.lineageId ?? reference.id;
|
|
203
|
+
const decidedAt = isoTimestampHoursBeforeBase(40 - index);
|
|
204
|
+
relations.push({
|
|
205
|
+
id: `preview-relevance-relation-${index}`,
|
|
206
|
+
relationState: "current",
|
|
207
|
+
predicate: "relevance_decision_is",
|
|
208
|
+
subjectKind: "reference",
|
|
209
|
+
subjectId: reference.id,
|
|
210
|
+
subjectLineageId: lineageId,
|
|
211
|
+
objectKind: "semanticNode",
|
|
212
|
+
objectId: `semantic-node-relevance-${plan.label}-v1`,
|
|
213
|
+
objectLineageId: `semantic-node-relevance-${plan.label}`,
|
|
214
|
+
subjectStateKey: `reference#${lineageId}#current`,
|
|
215
|
+
objectStateKey: `semanticNode#semantic-node-relevance-${plan.label}#current`,
|
|
216
|
+
objectSubjectStateKey: `semanticNode#semantic-node-relevance-${plan.label}#current#reference`,
|
|
217
|
+
predicateObjectStateKey: `relevance_decision_is#semanticNode#semantic-node-relevance-${plan.label}#current`,
|
|
218
|
+
subjectVersionKey: `reference#${reference.id}`,
|
|
219
|
+
objectVersionKey: `semanticNode#semantic-node-relevance-${plan.label}-v1`,
|
|
220
|
+
confidence: plan.confidence,
|
|
221
|
+
modelVersion: "4",
|
|
222
|
+
reviewRecommended: false,
|
|
223
|
+
metadata: JSON.stringify({
|
|
224
|
+
decisionId: `preview-decision-${index}`,
|
|
225
|
+
label: plan.label,
|
|
226
|
+
probabilities: { include: plan.label === "include" ? plan.confidence : 1 - plan.confidence, exclude: plan.label === "exclude" ? plan.confidence : 1 - plan.confidence },
|
|
227
|
+
version: 4,
|
|
228
|
+
decidedAt,
|
|
229
|
+
labels: ["include", "exclude"],
|
|
230
|
+
positiveLabel: "include",
|
|
231
|
+
question: "Should this source be used as evidence for the publication?",
|
|
232
|
+
cyclotronId: PREVIEW_CYCLOTRON_ID,
|
|
233
|
+
...(plan.provenance ? {
|
|
234
|
+
provenance: {
|
|
235
|
+
labelSet: ["include", "exclude"],
|
|
236
|
+
doctrineFingerprint: "3f9c2a71d80b46e5",
|
|
237
|
+
teachingFingerprint: "b71e04c9aa5d2f13",
|
|
238
|
+
teachingLabels: 37,
|
|
239
|
+
primingExamples: 6,
|
|
240
|
+
},
|
|
241
|
+
} : {}),
|
|
242
|
+
}),
|
|
243
|
+
} as SemanticRelationRecord);
|
|
244
|
+
if (plan.curation !== "none") {
|
|
245
|
+
const createdAt = isoTimestampHoursBeforeBase(30 - index);
|
|
246
|
+
messages.push({
|
|
247
|
+
id: `preview-curation-message-${index}`,
|
|
248
|
+
messageKind: "reference_curation",
|
|
249
|
+
messageDomain: "commentary",
|
|
250
|
+
status: "active",
|
|
251
|
+
summary: `${reference.title ?? lineageId}: curated`,
|
|
252
|
+
authorLabel: plan.curation === "delegated" ? "Maya Okafor" : null,
|
|
253
|
+
source: "newsroom",
|
|
254
|
+
createdAt,
|
|
255
|
+
updatedAt: createdAt,
|
|
256
|
+
metadata: JSON.stringify({
|
|
257
|
+
action: reference.curationStatus === "rejected" ? "reject" : "accept",
|
|
258
|
+
curationStatus: reference.curationStatus,
|
|
259
|
+
referenceLineageId: lineageId,
|
|
260
|
+
actorKind: plan.curation === "service" ? "service" : "human",
|
|
261
|
+
...(plan.curation === "delegated" ? {
|
|
262
|
+
attestation: "delegated",
|
|
263
|
+
relayedBy: "newsroom-reporter",
|
|
264
|
+
delegationId: "curation-delegation-9b1f6c24-5d0e-4a7b-8c31-2e6f0a1d7b45",
|
|
265
|
+
instruction: "Reject that one, it duplicates the filing from Tuesday.",
|
|
266
|
+
} : {}),
|
|
267
|
+
}),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
return {
|
|
272
|
+
relations,
|
|
273
|
+
messages,
|
|
274
|
+
shadowAgreement: {
|
|
275
|
+
schema: SHADOW_AGREEMENT_SCHEMA,
|
|
276
|
+
comparisons: 23,
|
|
277
|
+
agreement: 0.87,
|
|
278
|
+
precision: 0.9,
|
|
279
|
+
recall: 0.82,
|
|
280
|
+
confusion: { truePositive: 9, falsePositive: 1, falseNegative: 2, trueNegative: 11 },
|
|
281
|
+
computedAt: isoTimestampHoursBeforeBase(3),
|
|
282
|
+
positiveLabel: "include",
|
|
283
|
+
loop: { enabled: true, stoppedBecause: "daily decision cap", lastSweepAt: isoTimestampHoursBeforeBase(3), maxDecisionsPerDay: 50 },
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The local preview stands in for the multi-tenant newsroom: choosing a publishing account in the account menu
|
|
3
|
+
* switches which publication's content the preview shows. The choice is a cookie so server and browser agree.
|
|
4
|
+
*/
|
|
5
|
+
export const LOCAL_PREVIEW_TENANT_COOKIE = "papyrus-preview-tenant";
|
|
6
|
+
export const LOCAL_PREVIEW_DEFAULT_TENANT_ID = "preview-papyrus";
|
|
7
|
+
|
|
8
|
+
export type LocalPreviewTenant = { accountId: string; name: string; role: string; fixtureHeadlineSet: number };
|
|
9
|
+
|
|
10
|
+
export const LOCAL_PREVIEW_TENANTS: readonly LocalPreviewTenant[] = [
|
|
11
|
+
{ accountId: LOCAL_PREVIEW_DEFAULT_TENANT_ID, name: "Papyrus", role: "publisher", fixtureHeadlineSet: 0 },
|
|
12
|
+
{ accountId: "preview-pilobolus", name: "Pilobolus", role: "editor", fixtureHeadlineSet: 1 },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export function previewTenantIdFromCookieHeader(cookieHeader: string | null | undefined): string {
|
|
16
|
+
const match = cookieHeader?.split(";").map(part => part.trim()).find(part => part.startsWith(`${LOCAL_PREVIEW_TENANT_COOKIE}=`));
|
|
17
|
+
const value = match ? decodeURIComponent(match.slice(LOCAL_PREVIEW_TENANT_COOKIE.length + 1)) : "";
|
|
18
|
+
return LOCAL_PREVIEW_TENANTS.some(tenant => tenant.accountId === value) ? value : LOCAL_PREVIEW_DEFAULT_TENANT_ID;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getLocalPreviewTenantId(): string {
|
|
22
|
+
return typeof document === "undefined" ? LOCAL_PREVIEW_DEFAULT_TENANT_ID : previewTenantIdFromCookieHeader(document.cookie);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function setLocalPreviewTenantId(accountId: string): void {
|
|
26
|
+
document.cookie = `${LOCAL_PREVIEW_TENANT_COOKIE}=${encodeURIComponent(accountId)}; path=/; max-age=31536000; samesite=lax`;
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PROSPECTUS_SESSION_COOKIE,
|
|
3
|
+
loadProspectusSessionSealingSecret,
|
|
4
|
+
openProspectusCookie,
|
|
5
|
+
type ProspectusSignedInSession,
|
|
6
|
+
} from "./prospectus-session";
|
|
7
|
+
import { tenantIdForProspectusAccount } from "./tenant-keys";
|
|
8
|
+
|
|
9
|
+
type CookieReader = { get(name: string): { value: string } | undefined };
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The tenant of the publishing account the signed-in person selected, read from the sealed Prospectus session
|
|
13
|
+
* cookie. Returns undefined on a site without Prospectus, with no session, or with no account selected, so the
|
|
14
|
+
* caller falls back to the site's own tenant (PAPYRUS_TENANT_ID).
|
|
15
|
+
*/
|
|
16
|
+
export async function selectedTenantIdFromCookies(cookies: CookieReader, loadSecret: () => Promise<string | null> = loadProspectusSessionSealingSecret): Promise<string | undefined> {
|
|
17
|
+
const sealingSecret = await loadSecret();
|
|
18
|
+
if (!sealingSecret) return undefined;
|
|
19
|
+
const session = openProspectusCookie<ProspectusSignedInSession>(sealingSecret, PROSPECTUS_SESSION_COOKIE, cookies.get(PROSPECTUS_SESSION_COOKIE)?.value);
|
|
20
|
+
if (!session || session.accessTokenExpiresAt * 1000 <= Date.now()) return undefined;
|
|
21
|
+
return tenantIdForProspectusAccount(session.selectedAccountId);
|
|
22
|
+
}
|
|
@@ -2,6 +2,15 @@ import type { SemanticRelationRecord } from "./category-repository";
|
|
|
2
2
|
|
|
3
3
|
export const RELEVANCE_RELATION = "relevance_decision_is";
|
|
4
4
|
|
|
5
|
+
/** What produced a suggestion beyond the model version, as the sweep recorded it. */
|
|
6
|
+
export type RelevanceProvenance = {
|
|
7
|
+
labelSet: string[];
|
|
8
|
+
doctrineFingerprint: string | null;
|
|
9
|
+
teachingFingerprint: string | null;
|
|
10
|
+
teachingLabels: number;
|
|
11
|
+
primingExamples: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
5
14
|
/** The relevance cyclotron's current decision about one reference, as the References tab shows it. */
|
|
6
15
|
export type RelevanceDecisionView = {
|
|
7
16
|
decisionRelationId: string;
|
|
@@ -19,6 +28,10 @@ export type RelevanceDecisionView = {
|
|
|
19
28
|
/** Thumbs for a two-class decision with a positive label; per-class buttons otherwise. */
|
|
20
29
|
reviewControl: "thumbs" | "labels";
|
|
21
30
|
cyclotronId: string | null;
|
|
31
|
+
/** When the cyclotron made the suggestion, if the decision recorded it. */
|
|
32
|
+
decidedAt: string | null;
|
|
33
|
+
/** Null for decisions made before provenance was recorded. */
|
|
34
|
+
provenance: RelevanceProvenance | null;
|
|
22
35
|
};
|
|
23
36
|
|
|
24
37
|
/** What an editor submits from the review control. */
|
|
@@ -49,6 +62,20 @@ function parseMetadata(value: unknown): Record<string, unknown> {
|
|
|
49
62
|
}
|
|
50
63
|
}
|
|
51
64
|
|
|
65
|
+
function parseProvenance(value: unknown): RelevanceProvenance | null {
|
|
66
|
+
const record = parseMetadata(value);
|
|
67
|
+
if (!Object.keys(record).length) return null;
|
|
68
|
+
const count = (entry: unknown) => (Number.isFinite(Number(entry)) ? Number(entry) : 0);
|
|
69
|
+
const text = (entry: unknown) => (typeof entry === "string" && entry ? entry : null);
|
|
70
|
+
return {
|
|
71
|
+
labelSet: Array.isArray(record.labelSet) ? record.labelSet.map(String) : [],
|
|
72
|
+
doctrineFingerprint: text(record.doctrineFingerprint),
|
|
73
|
+
teachingFingerprint: text(record.teachingFingerprint),
|
|
74
|
+
teachingLabels: count(record.teachingLabels),
|
|
75
|
+
primingExamples: count(record.primingExamples),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
52
79
|
/**
|
|
53
80
|
* Current decisions keyed by reference lineage, from relations the Newsroom
|
|
54
81
|
* already loads: no extra request per row.
|
|
@@ -77,6 +104,8 @@ export function currentRelevanceDecisions(relations: SemanticRelationRecord[]):
|
|
|
77
104
|
question: typeof metadata.question === "string" ? metadata.question : null,
|
|
78
105
|
reviewControl: metadata.reviewControl === "thumbs" ? "thumbs" : "labels",
|
|
79
106
|
cyclotronId: typeof metadata.cyclotronId === "string" ? metadata.cyclotronId : null,
|
|
107
|
+
decidedAt: typeof metadata.decidedAt === "string" ? metadata.decidedAt : null,
|
|
108
|
+
provenance: parseProvenance(metadata.provenance),
|
|
80
109
|
});
|
|
81
110
|
}
|
|
82
111
|
return decisions;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const TENANT_ID_ENVIRONMENT_VARIABLE = "PAPYRUS_TENANT_ID";
|
|
2
|
+
const TENANT_KEY_SEPARATOR = "#";
|
|
3
|
+
const TENANT_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
4
|
+
|
|
5
|
+
export function validateTenantId(tenantId: string): string {
|
|
6
|
+
if (!TENANT_ID_PATTERN.test(tenantId)) {
|
|
7
|
+
throw new Error(`Tenant id "${tenantId}" must be lowercase letters, digits and hyphens, starting with a letter or digit.`);
|
|
8
|
+
}
|
|
9
|
+
return tenantId;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function tenantIdFromEnvironment(environment: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
13
|
+
const configured = environment[TENANT_ID_ENVIRONMENT_VARIABLE]?.trim();
|
|
14
|
+
return configured ? validateTenantId(configured) : undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function tenantScopedValue(tenantId: string | undefined, value: string): string {
|
|
18
|
+
if (!tenantId) return value;
|
|
19
|
+
const prefix = `${tenantId}${TENANT_KEY_SEPARATOR}`;
|
|
20
|
+
return value.startsWith(prefix) ? value : `${prefix}${value}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function rowBelongsToTenant(tenantId: string | undefined, row: { tenantId?: string | null } | null | undefined): boolean {
|
|
24
|
+
if (!tenantId || !row) return true;
|
|
25
|
+
return row.tenantId === tenantId;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The tenant a Prospectus publishing account maps to: its id as lowercase letters, digits and hyphens. */
|
|
29
|
+
export function tenantIdForProspectusAccount(accountId: string | null | undefined): string | undefined {
|
|
30
|
+
const normalized = (accountId ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63);
|
|
31
|
+
return normalized ? validateTenantId(normalized) : undefined;
|
|
32
|
+
}
|
package/package.json
CHANGED
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"types": "./infra/amplify-app-shell.ts",
|
|
65
65
|
"default": "./infra/index.js"
|
|
66
66
|
},
|
|
67
|
+
"./components/administration-delegations-panel": "./components/administration-delegations-panel.tsx",
|
|
67
68
|
"./components/ai-elements/conversation": "./components/ai-elements/conversation.tsx",
|
|
68
69
|
"./components/ai-elements/model-selector": "./components/ai-elements/model-selector.tsx",
|
|
69
70
|
"./components/ai-elements/prompt-input": "./components/ai-elements/prompt-input.tsx",
|
|
@@ -76,6 +77,7 @@
|
|
|
76
77
|
"./components/article-video": "./components/article-video.tsx",
|
|
77
78
|
"./components/blog-page-background": "./components/blog-page-background.tsx",
|
|
78
79
|
"./components/board-card-excerpts": "./components/board-card-excerpts.tsx",
|
|
80
|
+
"./components/curation-delegation-client": "./components/curation-delegation-client.ts",
|
|
79
81
|
"./components/dev-sandbox-editor-sign-in": "./components/dev-sandbox-editor-sign-in.tsx",
|
|
80
82
|
"./components/markus-ir-preview": "./components/markus-ir-preview.tsx",
|
|
81
83
|
"./components/news-desk-client-provider": "./components/news-desk-client-provider.tsx",
|
|
@@ -110,6 +112,8 @@
|
|
|
110
112
|
"./components/reference-curation-controls": "./components/reference-curation-controls.tsx",
|
|
111
113
|
"./components/reference-cyclotron-priming": "./components/reference-cyclotron-priming.tsx",
|
|
112
114
|
"./components/reference-cyclotron-status": "./components/reference-cyclotron-status.tsx",
|
|
115
|
+
"./components/reference-cyclotron-suggestion": "./components/reference-cyclotron-suggestion.tsx",
|
|
116
|
+
"./components/reference-live-agreement": "./components/reference-live-agreement.tsx",
|
|
113
117
|
"./components/reference-priming": "./components/reference-priming.tsx",
|
|
114
118
|
"./components/reference-relevance-review": "./components/reference-relevance-review.tsx",
|
|
115
119
|
"./components/reference-source-preview": "./components/reference-source-preview.tsx",
|
|
@@ -172,6 +176,8 @@
|
|
|
172
176
|
"./lib/content-source-context": "./lib/content-source-context.ts",
|
|
173
177
|
"./lib/content-types": "./lib/content-types.ts",
|
|
174
178
|
"./lib/curation-client": "./lib/curation-client.ts",
|
|
179
|
+
"./lib/curation-delegation-helpers": "./lib/curation-delegation-helpers.ts",
|
|
180
|
+
"./lib/cyclotron-suggestions": "./lib/cyclotron-suggestions.ts",
|
|
175
181
|
"./lib/define-site": "./lib/define-site.ts",
|
|
176
182
|
"./lib/demo-amplify-outputs": "./lib/demo-amplify-outputs.ts",
|
|
177
183
|
"./lib/dev-sandbox-editor-auth": "./lib/dev-sandbox-editor-auth.ts",
|
|
@@ -192,6 +198,7 @@
|
|
|
192
198
|
"./lib/layout-types": "./lib/layout-types.ts",
|
|
193
199
|
"./lib/layout-warnings": "./lib/layout-warnings.ts",
|
|
194
200
|
"./lib/local-preview-seed": "./lib/local-preview-seed.ts",
|
|
201
|
+
"./lib/local-preview-tenant": "./lib/local-preview-tenant.ts",
|
|
195
202
|
"./lib/local-preview": "./lib/local-preview.ts",
|
|
196
203
|
"./lib/markus-body": "./lib/markus-body.ts",
|
|
197
204
|
"./lib/markus-ir": "./lib/markus-ir.ts",
|
|
@@ -234,6 +241,7 @@
|
|
|
234
241
|
"./lib/prospectus-session": "./lib/prospectus-session.ts",
|
|
235
242
|
"./lib/prospectus-sign-in-routes": "./lib/prospectus-sign-in-routes.ts",
|
|
236
243
|
"./lib/prospectus-site-identity": "./lib/prospectus-site-identity.ts",
|
|
244
|
+
"./lib/prospectus-tenant": "./lib/prospectus-tenant.ts",
|
|
237
245
|
"./lib/public-placeholder-config": "./lib/public-placeholder-config.ts",
|
|
238
246
|
"./lib/publication-cyclotron": "./lib/publication-cyclotron.ts",
|
|
239
247
|
"./lib/publication-items": "./lib/publication-items.ts",
|
|
@@ -267,6 +275,7 @@
|
|
|
267
275
|
"./lib/staging-gated-path": "./lib/staging-gated-path.ts",
|
|
268
276
|
"./lib/staging-preview-object": "./lib/staging-preview-object.ts",
|
|
269
277
|
"./lib/story-reporting-trail": "./lib/story-reporting-trail.ts",
|
|
278
|
+
"./lib/tenant-keys": "./lib/tenant-keys.ts",
|
|
270
279
|
"./lib/test-editor-override": "./lib/test-editor-override.ts",
|
|
271
280
|
"./lib/text-columns": "./lib/text-columns.ts",
|
|
272
281
|
"./lib/themed-image": "./lib/themed-image.ts",
|
|
@@ -365,7 +374,7 @@
|
|
|
365
374
|
"optional": true
|
|
366
375
|
}
|
|
367
376
|
},
|
|
368
|
-
"version": "1.
|
|
377
|
+
"version": "1.13.0",
|
|
369
378
|
"dependencies": {
|
|
370
379
|
"@aws-amplify/adapter-nextjs": "^1.7.3",
|
|
371
380
|
"@aws-crypto/sha256-js": "5.2.0",
|
package/routes.manifest.json
CHANGED