Skip to content

Commit 52e6477

Browse files
alex-strukclaude
andcommitted
Merge develop into AI-1445; consolidate OCR text views
Reconciles the OCR-results work that diverged: develop's ExtractedTextView (markdown rendering via react-markdown + sanitize, with a rendered/raw toggle) and this branch's OcrResults toggle (Extracted / Text / JSON). - OcrResults keeps the 3-way toggle shell + JSON debug view; its Text view now delegates to ExtractedTextView so Azure layout markdown renders safely instead of being dumped as split-by-newline <p> tags. - OcrResult.content uses develop's OcrContent type; dropped the duplicate inline content shape introduced here. - Fixed conditional-hook ordering (useState now runs before the null guard) and disabled toggles for views with no data. - DocumentViewerModal renders <OcrResults> guarded by develop's hasOcrData, preserving purged-document handling. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2 parents 8915e03 + 761bf09 commit 52e6477

109 files changed

Lines changed: 13160 additions & 4785 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/backend-qa.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ on:
55
pull_request:
66
types: [opened, synchronize, reopened]
77
branches: [main, develop]
8+
paths:
9+
- 'apps/backend-services/**'
10+
- 'apps/shared/**'
11+
- 'packages/**'
12+
- '.github/workflows/backend-qa.yml'
813
workflow_dispatch:
914

1015
jobs:
@@ -33,13 +38,18 @@ jobs:
3338
working-directory: apps/backend-services
3439
run: npm run db:generate
3540
env:
36-
DATABASE_URL: "file:dev.db"
41+
DATABASE_URL: "postgresql://localhost:5432/dummy"
3742
- name: Lint
3843
working-directory: apps/backend-services
3944
run: npm run lint
4045
- name: Type Check
4146
working-directory: apps/backend-services
4247
run: npm run type-check
48+
- name: Build
49+
working-directory: apps/backend-services
50+
run: npm run build
51+
env:
52+
DATABASE_URL: "postgresql://localhost:5432/dummy"
4353
- name: Test
4454
working-directory: apps/backend-services
4555
run: npm run test:cov

.github/workflows/deploy-instance.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,25 @@ jobs:
443443
"app.kubernetes.io/instance=${INSTANCE_NAME}" --overwrite \
444444
-n "${OC_NAMESPACE}" || true
445445
446+
- name: Delete immutable PLG StatefulSets before upgrade
447+
if: env.DEPLOY_PLG == 'true'
448+
env:
449+
INSTANCE_NAME: ${{ needs.metadata.outputs.instance-name }}
450+
OC_NAMESPACE: ${{ needs.metadata.outputs.namespace || secrets.OPENSHIFT_NAMESPACE }}
451+
run: |
452+
set -euo pipefail
453+
PLG_RELEASE="${INSTANCE_NAME}-plg"
454+
# StatefulSet volumeClaimTemplates are immutable in Kubernetes.
455+
# Delete the StatefulSets so Helm can recreate them with the correct
456+
# spec. PVCs are not deleted and data is preserved.
457+
for ss in loki prometheus; do
458+
SS_NAME="${PLG_RELEASE}-${ss}"
459+
if oc get statefulset "${SS_NAME}" -n "${OC_NAMESPACE}" &>/dev/null; then
460+
echo "Deleting StatefulSet ${SS_NAME} for clean upgrade..."
461+
oc delete statefulset "${SS_NAME}" -n "${OC_NAMESPACE}" --cascade=orphan
462+
fi
463+
done
464+
446465
- name: Deploy PLG monitoring stack
447466
if: env.DEPLOY_PLG == 'true'
448467
env:

.github/workflows/frontend-qa.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ on:
55
pull_request:
66
types: [opened, synchronize, reopened]
77
branches: [main, develop]
8+
paths:
9+
- 'apps/frontend/**'
10+
- 'packages/**'
11+
- '.github/workflows/frontend-qa.yml'
812
workflow_dispatch:
913

1014
jobs:

.github/workflows/temporal-qa.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ on:
55
pull_request:
66
types: [opened, synchronize, reopened]
77
branches: [main, develop]
8+
paths:
9+
- 'apps/temporal/**'
10+
- 'apps/shared/**'
11+
- 'packages/**'
12+
- '.github/workflows/temporal-qa.yml'
813
workflow_dispatch:
914

1015
jobs:
@@ -41,6 +46,11 @@ jobs:
4146
- name: Type Check
4247
working-directory: apps/temporal
4348
run: npm run type-check
49+
- name: Build
50+
working-directory: apps/temporal
51+
run: npm run build
52+
env:
53+
DATABASE_URL: "postgresql://localhost:5432/dummy"
4454
- name: Run tests
4555
working-directory: apps/temporal
4656
run: npm test

apps/backend-services/Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ RUN cd /packages/blob-storage-paths && npm install --ignore-scripts && npm run b
2323
COPY packages/graph-workflow /packages/graph-workflow
2424
RUN cd /packages/graph-workflow && npm install --ignore-scripts && npm run build
2525

26+
COPY packages/temporal-payload-codec /packages/temporal-payload-codec
27+
RUN cd /packages/temporal-payload-codec && npm install --ignore-scripts && npm run build
28+
2629
COPY packages/monitoring /packages/monitoring
2730
RUN mkdir -p /packages/monitoring/node_modules/@ai-di && \
2831
ln -s /packages/logging /packages/monitoring/node_modules/@ai-di/shared-logging && \
@@ -65,6 +68,7 @@ COPY --from=builder /packages/logging /packages/logging
6568
COPY --from=builder /packages/graph-insertion-slots /packages/graph-insertion-slots
6669
COPY --from=builder /packages/blob-storage-paths /packages/blob-storage-paths
6770
COPY --from=builder /packages/graph-workflow /packages/graph-workflow
71+
COPY --from=builder /packages/temporal-payload-codec /packages/temporal-payload-codec
6872
COPY --from=builder /packages/monitoring /packages/monitoring
6973

7074
# Copy package files

apps/backend-services/package.json

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
"private": true,
77
"license": "Apache-2.0",
88
"scripts": {
9-
"build": "npm run db:generate && npm run build:graph-insertion-slots && npm run build:graph-workflow-config && npm run build:graph-workflow && nest build",
10-
"build:prod": "NODE_ENV=production npm run db:generate && npm run build:graph-insertion-slots && npm run build:graph-workflow-config && npm run build:graph-workflow && nest build",
9+
"build": "npm run db:generate && npm run build:graph-insertion-slots && npm run build:graph-workflow && nest build",
10+
"build:prod": "NODE_ENV=production npm run db:generate && npm run build:graph-insertion-slots && npm run build:graph-workflow && nest build",
1111
"build:logging": "cd ../../packages/logging && npm run build",
1212
"format": "prettier --write \"src/**/*.ts\"",
1313
"start": "nest start",
@@ -31,15 +31,13 @@
3131
"workflow:migrate-ocr-refs": "tsx scripts/migrate-workflow-config-ocr-refs.ts",
3232
"workflow:migrate-ocr-refs:apply": "tsx scripts/migrate-workflow-config-ocr-refs.ts --apply --refresh-benchmark-hashes",
3333
"build:graph-workflow": "cd ../../packages/graph-workflow && npm run build",
34-
"build:graph-workflow-config": "cd ../../packages/graph-workflow-config && npm run build",
3534
"build:graph-insertion-slots": "cd ../../packages/graph-insertion-slots && npm run build"
3635
},
3736
"dependencies": {
3837
"@ai-di/blob-storage-paths": "file:../../packages/blob-storage-paths",
39-
"@ai-di/graph-workflow-config": "file:../../packages/graph-workflow-config",
40-
"@ai-di/temporal-payload-codec": "file:../../packages/temporal-payload-codec",
4138
"@ai-di/graph-insertion-slots": "file:../../packages/graph-insertion-slots",
4239
"@ai-di/graph-workflow": "file:../../packages/graph-workflow",
40+
"@ai-di/temporal-payload-codec": "file:../../packages/temporal-payload-codec",
4341
"@ai-di/monitoring": "file:../../packages/monitoring",
4442
"@ai-di/shared-logging": "file:../../packages/logging",
4543
"@aws-sdk/client-s3": "3.990.0",

apps/backend-services/src/benchmark/benchmark-definition-db.service.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export class BenchmarkDefinitionDbService {
105105
async findBenchmarkProject(
106106
id: string,
107107
tx?: Prisma.TransactionClient,
108-
): Promise<{ id: string } | null> {
108+
): Promise<{ id: string; group_id: string } | null> {
109109
const client = tx ?? this.prisma;
110110
return client.benchmarkProject.findUnique({ where: { id } });
111111
}
@@ -120,11 +120,14 @@ export class BenchmarkDefinitionDbService {
120120
async findDatasetVersion(
121121
id: string,
122122
tx?: Prisma.TransactionClient,
123-
): Promise<{ id: string; dataset: { name: string } } | null> {
123+
): Promise<{
124+
id: string;
125+
dataset: { name: string; group_id: string };
126+
} | null> {
124127
const client = tx ?? this.prisma;
125128
return client.datasetVersion.findUnique({
126129
where: { id },
127-
include: { dataset: { select: { name: true } } },
130+
include: { dataset: { select: { name: true, group_id: true } } },
128131
});
129132
}
130133

apps/backend-services/src/benchmark/benchmark-definition.service.spec.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ describe("BenchmarkDefinitionService", () => {
8686
createdAt: new Date(),
8787
dataset: {
8888
name: "Test Dataset",
89+
group_id: "test-group",
8990
},
9091
};
9192

@@ -114,6 +115,7 @@ describe("BenchmarkDefinitionService", () => {
114115
lineage: {
115116
id: "workflow-1",
116117
name: "Test Workflow",
118+
group_id: "test-group",
117119
},
118120
};
119121

@@ -502,6 +504,44 @@ describe("BenchmarkDefinitionService", () => {
502504
);
503505
});
504506

507+
it("rejects a dataset version owned by another group (cross-group reference blocked)", async () => {
508+
jest
509+
.spyOn(prisma.benchmarkProject, "findUnique")
510+
.mockResolvedValue(mockProject); // group "test-group"
511+
// Dataset version exists but its dataset belongs to a different group.
512+
jest.spyOn(prisma.datasetVersion, "findUnique").mockResolvedValue({
513+
...mockDatasetVersion,
514+
dataset: { name: "Other", group_id: "other-group" },
515+
});
516+
517+
await expect(
518+
service.createDefinition("project-1", createDto),
519+
).rejects.toThrow(
520+
'Dataset version with ID "ds-version-1" does not exist',
521+
);
522+
});
523+
524+
it("rejects a workflow version owned by another group (cross-group reference blocked)", async () => {
525+
jest
526+
.spyOn(prisma.benchmarkProject, "findUnique")
527+
.mockResolvedValue(mockProject); // group "test-group"
528+
jest
529+
.spyOn(prisma.datasetVersion, "findUnique")
530+
.mockResolvedValue(mockDatasetVersion);
531+
jest.spyOn(prisma.split, "findUnique").mockResolvedValue(mockSplit);
532+
// Workflow version exists but its lineage belongs to a different group.
533+
jest.spyOn(prisma.workflowVersion, "findUnique").mockResolvedValue({
534+
...mockWorkflowVersion,
535+
lineage: { ...mockWorkflowVersion.lineage, group_id: "other-group" },
536+
});
537+
538+
await expect(
539+
service.createDefinition("project-1", createDto),
540+
).rejects.toThrow(
541+
'Workflow version with ID "wv-workflow-1" does not exist',
542+
);
543+
});
544+
505545
it("returns 400 when evaluator type is not registered", async () => {
506546
jest
507547
.spyOn(prisma.benchmarkProject, "findUnique")
@@ -701,6 +741,9 @@ describe("BenchmarkDefinitionService", () => {
701741
jest
702742
.spyOn(prisma.benchmarkDefinition, "findFirst")
703743
.mockResolvedValue(existingDefinition as never);
744+
jest
745+
.spyOn(prisma.benchmarkProject, "findUnique")
746+
.mockResolvedValue(mockProject);
704747
jest.spyOn(prisma.benchmarkDefinition, "update").mockResolvedValue({
705748
...existingDefinition,
706749
immutable: true,
@@ -770,6 +813,9 @@ describe("BenchmarkDefinitionService", () => {
770813
jest
771814
.spyOn(prisma.benchmarkDefinition, "findFirst")
772815
.mockResolvedValue(existingDefinition as never);
816+
jest
817+
.spyOn(prisma.benchmarkProject, "findUnique")
818+
.mockResolvedValue(mockProject);
773819

774820
const updatedDefinition = {
775821
...existingDefinition,
@@ -1043,16 +1089,27 @@ describe("BenchmarkDefinitionService", () => {
10431089
const baseLineageId = "base-lineage";
10441090
const newBaseVersionId = "wv-new-base";
10451091

1092+
jest
1093+
.spyOn(prisma.benchmarkProject, "findUnique")
1094+
.mockResolvedValue(mockProject);
1095+
10461096
jest.spyOn(prisma.workflowVersion, "findUnique").mockResolvedValue({
10471097
id: candidateWorkflowVersionId,
10481098
config: candidateConfig,
10491099
lineage: {
10501100
id: "cand-lineage",
1101+
group_id: "test-group",
10511102
workflow_kind: "benchmark_candidate",
10521103
source_workflow_id: baseLineageId,
10531104
},
10541105
} as never);
10551106

1107+
jest.spyOn(prisma.workflowLineage, "findUnique").mockResolvedValue({
1108+
id: baseLineageId,
1109+
group_id: "test-group",
1110+
head_version_id: "wv-old-head",
1111+
} as never);
1112+
10561113
jest.spyOn(prisma.workflowVersion, "findFirst").mockResolvedValue({
10571114
version_number: 2,
10581115
} as never);
@@ -1107,16 +1164,27 @@ describe("BenchmarkDefinitionService", () => {
11071164
const baseLineageId = "base-lineage";
11081165
const candidateLineageId = "cand-lineage";
11091166

1167+
jest
1168+
.spyOn(prisma.benchmarkProject, "findUnique")
1169+
.mockResolvedValue(mockProject);
1170+
11101171
jest.spyOn(prisma.workflowVersion, "findUnique").mockResolvedValue({
11111172
id: candidateWorkflowVersionId,
11121173
config: candidateConfig,
11131174
lineage: {
11141175
id: candidateLineageId,
1176+
group_id: "test-group",
11151177
workflow_kind: "benchmark_candidate",
11161178
source_workflow_id: baseLineageId,
11171179
},
11181180
} as never);
11191181

1182+
jest.spyOn(prisma.workflowLineage, "findUnique").mockResolvedValue({
1183+
id: baseLineageId,
1184+
group_id: "test-group",
1185+
head_version_id: "wv-old-head",
1186+
} as never);
1187+
11201188
jest.spyOn(prisma.workflowVersion, "findFirst").mockResolvedValue({
11211189
version_number: 2,
11221190
} as never);
@@ -1195,11 +1263,15 @@ describe("BenchmarkDefinitionService", () => {
11951263
});
11961264

11971265
it("rejects non-candidate workflows", async () => {
1266+
jest
1267+
.spyOn(prisma.benchmarkProject, "findUnique")
1268+
.mockResolvedValue(mockProject);
11981269
jest.spyOn(prisma.workflowVersion, "findUnique").mockResolvedValue({
11991270
id: "wv-primary",
12001271
config: candidateConfig,
12011272
lineage: {
12021273
id: "primary-lineage",
1274+
group_id: "test-group",
12031275
workflow_kind: "primary",
12041276
source_workflow_id: null,
12051277
},
@@ -1210,6 +1282,37 @@ describe("BenchmarkDefinitionService", () => {
12101282
).rejects.toThrow(/not a benchmark candidate/i);
12111283
});
12121284

1285+
it("rejects a candidate whose source points at another group's base lineage (cross-group write blocked)", async () => {
1286+
// Candidate is in the caller's group, but its source_workflow_id points at
1287+
// a lineage owned by a different group. The base-lineage group check must
1288+
// reject this before any version is written.
1289+
jest
1290+
.spyOn(prisma.benchmarkProject, "findUnique")
1291+
.mockResolvedValue(mockProject);
1292+
jest.spyOn(prisma.workflowVersion, "findUnique").mockResolvedValue({
1293+
id: "candidate-v1",
1294+
config: candidateConfig,
1295+
lineage: {
1296+
id: "cand-lineage",
1297+
group_id: "test-group",
1298+
workflow_kind: "benchmark_candidate",
1299+
source_workflow_id: "foreign-base-lineage",
1300+
},
1301+
} as never);
1302+
// Base lineage belongs to another group.
1303+
jest.spyOn(prisma.workflowLineage, "findUnique").mockResolvedValue({
1304+
id: "foreign-base-lineage",
1305+
group_id: "other-group",
1306+
head_version_id: "wv-foreign-head",
1307+
} as never);
1308+
const createSpy = jest.spyOn(prisma.workflowVersion, "create");
1309+
1310+
await expect(
1311+
service.applyToBaseWorkflow("project-1", "candidate-v1", false),
1312+
).rejects.toThrow(NotFoundException);
1313+
expect(createSpy).not.toHaveBeenCalled();
1314+
});
1315+
12131316
it("throws ConflictException when definition pin does not match lineage head inside transaction", async () => {
12141317
const projectId = "project-1";
12151318
const definitionId = "def-1";
@@ -1235,6 +1338,10 @@ describe("BenchmarkDefinitionService", () => {
12351338
edges: [],
12361339
};
12371340

1341+
jest
1342+
.spyOn(prisma.benchmarkProject, "findUnique")
1343+
.mockResolvedValue({ ...mockProject, group_id: baseLineageGroupId });
1344+
12381345
let findFirstCalls = 0;
12391346
jest
12401347
.spyOn(prisma.benchmarkDefinition, "findFirst")
@@ -1315,6 +1422,10 @@ describe("BenchmarkDefinitionService", () => {
13151422
.spyOn(service, "getDefinitionById")
13161423
.mockResolvedValue({ id: definitionId } as never);
13171424

1425+
jest
1426+
.spyOn(prisma.benchmarkProject, "findUnique")
1427+
.mockResolvedValue({ ...mockProject, group_id: baseLineageGroupId });
1428+
13181429
jest.spyOn(prisma.benchmarkDefinition, "findFirst").mockResolvedValue({
13191430
id: definitionId,
13201431
projectId,

0 commit comments

Comments
 (0)