Skip to content

Commit 3866aef

Browse files
committed
Catalog hides sunset blocks unless includeSunset; enrichment get returns the group's run state and outputs; knowledge upload prints the document fields; docs chunks titled by page not nav link; logs carry hasHandledErrors and opt-in handledErrorRuns; outputTable receipt keeps stdout
1 parent c9878a8 commit 3866aef

46 files changed

Lines changed: 1095 additions & 106 deletions

Some content is hidden

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

apps/docs/openapi-v2-logs.json

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,16 @@
218218
"minLength": 1
219219
}
220220
},
221+
{
222+
"name": "includeHandledErrors",
223+
"in": "query",
224+
"required": false,
225+
"description": "Whether `level=error` also selects runs that finished at `info` after a block error was recovered by an error path. Off by default: such a run succeeded, so it is an error only to a caller auditing error handling. Every row reports `hasHandledErrors` whether or not this is set. Job runs carry no block trace, so the flag never widens that branch.",
226+
"schema": {
227+
"description": "Whether `level=error` also selects runs that finished at `info` after a block error was recovered by an error path. Off by default: such a run succeeded, so it is an error only to a caller auditing error handling. Every row reports `hasHandledErrors` whether or not this is set. Job runs carry no block trace, so the flag never widens that branch.",
228+
"type": "boolean"
229+
}
230+
},
221231
{
222232
"name": "status",
223233
"in": "query",
@@ -477,6 +487,16 @@
477487
"description": "Severity level to include."
478488
}
479489
},
490+
{
491+
"name": "includeHandledErrors",
492+
"in": "query",
493+
"required": false,
494+
"description": "Whether runs with a handled block error are counted as `handledErrorRuns`, and whether `level=error` also selects them. Off by default: counting them scans each run’s stored trace.",
495+
"schema": {
496+
"description": "Whether runs with a handled block error are counted as `handledErrorRuns`, and whether `level=error` also selects them. Off by default: counting them scans each run’s stored trace.",
497+
"type": "boolean"
498+
}
499+
},
480500
{
481501
"name": "startDate",
482502
"in": "query",
@@ -951,6 +971,10 @@
951971
],
952972
"description": "Files the run produced, or null when none are recorded. Only the run's own output files appear; input attachments a caller supplied are addressed through the files API instead."
953973
},
974+
"hasHandledErrors": {
975+
"type": "boolean",
976+
"description": "Whether a block in the run errored and was recovered by an error path. Such a run keeps `level: info`, so this is the only place the handled error shows at run level; pass `includeHandledErrors=true` with `level=error` to list these runs. Always false for a job run."
977+
},
954978
"workflow": {
955979
"type": "object",
956980
"properties": {
@@ -1012,7 +1036,8 @@
10121036
"endedAt",
10131037
"totalDurationMs",
10141038
"cost",
1015-
"files"
1039+
"files",
1040+
"hasHandledErrors"
10161041
],
10171042
"additionalProperties": false,
10181043
"title": "Execution log summary",
@@ -1260,6 +1285,7 @@
12601285
"cost": {
12611286
"total": 0.0032
12621287
},
1288+
"hasHandledErrors": false,
12631289
"files": [
12641290
{
12651291
"id": "f1c3a7d0-4b52-4a8e-9f61-2d7c8b3e5a04",
@@ -1798,6 +1824,10 @@
17981824
"type": "number",
17991825
"description": "Runs in the window that errored."
18001826
},
1827+
"handledErrorRuns": {
1828+
"description": "Runs in the window in which a block errored and was recovered by an error path. Such runs count as successful in every other figure. Present only when `includeHandledErrors=true`.",
1829+
"type": "number"
1830+
},
18011831
"avgLatency": {
18021832
"type": "number",
18031833
"description": "Mean run duration in milliseconds across the window, weighted by run count."

apps/docs/openapi-v2-resources.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3639,6 +3639,16 @@
36393639
"enum": ["builtin", "custom"]
36403640
}
36413641
},
3642+
{
3643+
"name": "includeSunset",
3644+
"in": "query",
3645+
"required": false,
3646+
"description": "Include `legacy` and `deprecated` blocks. Off by default: a sunset block keeps executing where it is already placed, but it is not offered for new authoring. Each returned entry carries `sunset.replacedBy`, the block to build with instead.",
3647+
"schema": {
3648+
"description": "Include `legacy` and `deprecated` blocks. Off by default: a sunset block keeps executing where it is already placed, but it is not offered for new authoring. Each returned entry carries `sunset.replacedBy`, the block to build with instead.",
3649+
"type": "boolean"
3650+
}
3651+
},
36423652
{
36433653
"name": "sortBy",
36443654
"in": "query",

apps/docs/openapi-v2-tables.json

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2637,8 +2637,8 @@
26372637
},
26382638
"get": {
26392639
"operationId": "getRowEnrichment",
2640-
"summary": "Get Enrichment Run Detail",
2641-
"description": "Retrieve the provider cascade behind one enrichment cell: every configured provider in cascade order, each one's status, hosted-key cost, and duration, plus which provider produced the match. `null` means the cell has never run, or ran before cascade detail was recorded — distinct from a `404`, which means the table, row, or group does not exist.",
2640+
"summary": "Get Row Group Run",
2641+
"description": "Retrieve one workflow or enrichment group's outcome on one row: the run state `includeRunState` reports on the row endpoints (`status`, `error`, `workflowId`, `executionId`, …), the group's output cells keyed by column name, and — for an enrichment group — the provider cascade behind them: every configured provider in cascade order, each one's status, hosted-key cost, and duration, plus which provider produced the match. A row that exists always answers; `runState: null` means the group has never run for it, and `cascade: null` that no provider breakdown was recorded. A `404` means the table, row, or group does not exist.",
26422642
"tags": ["Tables"],
26432643
"parameters": [
26442644
{
@@ -2688,7 +2688,7 @@
26882688
],
26892689
"responses": {
26902690
"200": {
2691-
"description": "The enrichment run detail, or null when none was recorded.",
2691+
"description": "The run state, output cells, and provider cascade for the group on the row.",
26922692
"headers": {
26932693
"X-RateLimit-Limit": {
26942694
"$ref": "#/components/headers/X-RateLimit-Limit"
@@ -9983,10 +9983,29 @@
99839983
"title": "Enrichment provider outcome",
99849984
"description": "One provider's result within an enrichment cascade."
99859985
},
9986-
"V2RowEnrichmentResponse": {
9986+
"V2RowGroupEnrichment": {
99879987
"type": "object",
99889988
"properties": {
9989-
"data": {
9989+
"groupId": {
9990+
"type": "string",
9991+
"description": "Workflow or enrichment group this answers for."
9992+
},
9993+
"runState": {
9994+
"anyOf": [
9995+
{
9996+
"$ref": "#/components/schemas/V2TableRowRunState"
9997+
},
9998+
{
9999+
"type": "null"
10000+
}
10001+
],
10002+
"description": "Most recent run of this group on this row — the same shape `includeRunState` reports — or null when the group has never run for the row."
10003+
},
10004+
"outputs": {
10005+
"description": "The group's output cells keyed by column name. A column the run has not populated is null.",
10006+
"$ref": "#/components/schemas/V2TableRowData"
10007+
},
10008+
"cascade": {
999010009
"anyOf": [
999110010
{
999210011
"$ref": "#/components/schemas/V2EnrichmentRunDetail"
@@ -9995,13 +10014,26 @@
999510014
"type": "null"
999610015
}
999710016
],
9998-
"description": "Response data."
10017+
"description": "Provider cascade behind the cell, or null when none was recorded — a manual workflow group, a group that has not run, or a run predating the breakdown."
10018+
}
10019+
},
10020+
"required": ["groupId", "runState", "outputs", "cascade"],
10021+
"additionalProperties": false,
10022+
"title": "Row group enrichment",
10023+
"description": "Run state, output cells, and provider cascade for one group on one row."
10024+
},
10025+
"V2RowEnrichmentResponse": {
10026+
"type": "object",
10027+
"properties": {
10028+
"data": {
10029+
"description": "Response data.",
10030+
"$ref": "#/components/schemas/V2RowGroupEnrichment"
999910031
}
1000010032
},
1000110033
"required": ["data"],
1000210034
"additionalProperties": false,
1000310035
"title": "Row enrichment response",
10004-
"description": "Provider cascade, cost, and timing for one enrichment cell."
10036+
"description": "Run state, output cells, and provider cascade for one group on one row."
1000510037
},
1000610038
"V2TableRunDispatch": {
1000710039
"type": "object",

apps/sim/app/api/v2/blocks/route.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ function blockCursor({
7171
category,
7272
capability,
7373
source,
74+
includeSunset = false,
7475
sortBy = 'id',
7576
sortOrder = 'asc',
7677
}: {
@@ -79,6 +80,7 @@ function blockCursor({
7980
category?: string
8081
capability?: string
8182
source?: string
83+
includeSunset?: boolean
8284
sortBy?: string
8385
sortOrder?: string
8486
}): string {
@@ -90,6 +92,7 @@ function blockCursor({
9092
category,
9193
capability,
9294
source,
95+
includeSunset,
9396
}),
9497
offset
9598
)
@@ -123,6 +126,7 @@ describe('/api/v2/blocks', () => {
123126
category: undefined,
124127
capability: undefined,
125128
source: undefined,
129+
includeSunset: false,
126130
sortBy: 'id',
127131
sortOrder: 'asc',
128132
limit: 50,
@@ -133,6 +137,25 @@ describe('/api/v2/blocks', () => {
133137
})
134138
})
135139

140+
it('asks for sunset blocks only when includeSunset is set, and stamps it into the cursor', async () => {
141+
const response = await GET(
142+
request(`/api/v2/blocks?workspaceId=${WORKSPACE_ID}&includeSunset=true`)
143+
)
144+
145+
expect(response.status).toBe(200)
146+
expect(mocks.list).toHaveBeenCalledWith(
147+
expect.objectContaining({ input: expect.objectContaining({ includeSunset: true }) })
148+
)
149+
150+
const cursor = blockCursor({ offset: 2 })
151+
const replayed = await GET(
152+
request(
153+
`/api/v2/blocks?workspaceId=${WORKSPACE_ID}&includeSunset=true&cursor=${encodeURIComponent(cursor)}`
154+
)
155+
)
156+
expect(replayed.status).toBe(400)
157+
})
158+
136159
it('resumes from the offset cursor and mints the next one while pages remain', async () => {
137160
mocks.list.mockResolvedValue({ entries: [summary], hasMore: true, offset: 2, limit: 2 })
138161
const cursor = blockCursor({ offset: 2 })

apps/sim/app/api/v2/blocks/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ function blockCursorFilters(query: V2ListBlocksQuery) {
1717
category: query.category,
1818
capability: query.capability,
1919
source: query.source,
20+
includeSunset: query.includeSunset,
2021
})
2122
}
2223

apps/sim/app/api/v2/knowledge/[knowledgeBaseId]/documents/uploads/[uploadId]/complete/route.test.ts

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -41,33 +41,6 @@ vi.mock('@/lib/core/telemetry', () => ({
4141
PlatformEvents: { knowledgeBaseDocumentsUploaded: mocks.platformEvent },
4242
}))
4343
vi.mock('@/lib/posthog/server', () => ({ captureServerEvent: mocks.captureServerEvent }))
44-
vi.mock('@/app/api/v2/knowledge/[knowledgeBaseId]/documents/uploads/utils', () => ({
45-
toV2KnowledgeDocumentUpload: (_session: unknown, document: { id: string } | null) => ({
46-
id: 'upload-1',
47-
knowledgeBaseId: 'kb-1',
48-
status: 'completed',
49-
name: 'guide.pdf',
50-
contentType: 'application/pdf',
51-
size: 1024,
52-
expiresAt: '2026-08-04T21:00:00.000Z',
53-
error: null,
54-
document: document
55-
? {
56-
id: document.id,
57-
knowledgeBaseId: 'kb-1',
58-
filename: 'guide.pdf',
59-
fileSize: 1024,
60-
mimeType: 'application/pdf',
61-
processingStatus: 'pending',
62-
chunkCount: 0,
63-
tokenCount: 0,
64-
characterCount: 0,
65-
enabled: true,
66-
createdAt: '2026-08-03T21:01:00.000Z',
67-
}
68-
: null,
69-
}),
70-
}))
7144

7245
import { POST } from '@/app/api/v2/knowledge/[knowledgeBaseId]/documents/uploads/[uploadId]/complete/route'
7346

@@ -84,8 +57,19 @@ const DOCUMENT = {
8457
enabled: true,
8558
uploadedAt: new Date('2026-08-03T21:01:00.000Z'),
8659
}
60+
/** The session as the completion use case hands it back, in storage shape. */
61+
const SESSION = {
62+
id: 'upload-1',
63+
knowledgeBaseId: 'kb-1',
64+
status: 'completed',
65+
fileName: 'guide.pdf',
66+
contentType: 'application/pdf',
67+
fileSize: 1024,
68+
expiresAt: new Date('2026-08-04T21:00:00.000Z'),
69+
error: null,
70+
}
8771
const RESULT = {
88-
session: { id: 'upload-1' },
72+
session: SESSION,
8973
value: { document: DOCUMENT, created: true, knowledgeBaseName: 'Docs' },
9074
alreadyCompleted: false,
9175
workspaceId: WORKSPACE_ID,
@@ -160,6 +144,32 @@ describe('POST knowledge-document upload completion', () => {
160144
expect(await response.json()).toMatchObject({ data: { document: { id: 'upload-1' } } })
161145
})
162146

147+
/**
148+
* `knowledge documents upload` answered `filename: null, processingStatus: null`:
149+
* the created row must come back under its own names, freshly `pending`
150+
* with no chunks yet, not only as a session receipt.
151+
*/
152+
it('publishes the created row with its filename, pending status, and zero chunks', async () => {
153+
mocks.completeUpload.mockResolvedValue(RESULT)
154+
155+
const response = await request().response
156+
const body = await response.json()
157+
158+
expect(response.status).toBe(200)
159+
expect(body.data).toMatchObject({
160+
id: 'upload-1',
161+
status: 'completed',
162+
name: 'guide.pdf',
163+
document: {
164+
id: 'upload-1',
165+
filename: 'guide.pdf',
166+
processingStatus: 'pending',
167+
chunkCount: 0,
168+
createdAt: '2026-08-03T21:01:00.000Z',
169+
},
170+
})
171+
})
172+
163173
it('does not duplicate analytics for an idempotent completion retry', async () => {
164174
mocks.completeUpload.mockResolvedValue({
165175
...RESULT,

0 commit comments

Comments
 (0)