Skip to content

Commit d54e151

Browse files
kubohiroyacodex
andauthored
test(fdm): cover workflow and ruleset projections (#1751)
Co-authored-by: codex <codex@example.com>
1 parent 7d9e766 commit d54e151

4 files changed

Lines changed: 166 additions & 3 deletions

File tree

docs/fdm-seven-layer-contract-audit.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ The initial implementation keeps node data version `1` and preserves existing se
6262
- Payloads that specify both old and new aliases with conflicting values are rejected.
6363
- Axis maps still contain the four visible dashboard slots, but each slot can choose either legacy or canonical axis names.
6464
- `fdmSweep` is the primary FDM sweep mutation. `fdmVerify` remains a compatibility mutation while upstream keeps exposing it.
65-
- Dedicated run/job lifecycle, workflow projection, ruleset governance, and lifecycle-aware space mutation stay gated until the matching ide-gsm schema capabilities are available.
65+
- Dedicated run/job lifecycle and lifecycle-aware space mutation stay gated until the matching ide-gsm schema capabilities are available. L4 workflow and L5 ruleset projections are represented as optional server-provided dashboard projections; unavailable, stale, and unsupported states are displayed without client-side inference.
6666

6767
## Dashboard adapter implementation
6868

@@ -89,8 +89,8 @@ Phase B1 now keeps each layer represented by an executable fixture or an explici
8989
| L1 parameter set | `parameterSet` / `profile` saved-node normalization | covered |
9090
| L2 range snapshot | `resultRef` to `snapshot.resultRef` projection | covered |
9191
| L3 run/job | runtime-event bridge payload and forbidden credential-key rejection | covered |
92-
| L4 workflow | workflow projection acceptance and unknown enum rejection | covered |
93-
| L5 ruleset | ruleset governance projection acceptance | covered |
92+
| L4 workflow | workflow projection acceptance, full known enum coverage, unknown enum rejection, and representative UI states | covered |
93+
| L5 ruleset | ruleset governance projection acceptance, fingerprint/digest display, missing issue-number visibility, and unavailable/stale UI states | covered |
9494
| L6 space | space catalog metadata with explicit unavailable or unscoped capability provenance | covered, pending #1735/#1736 action/view expansion |
9595

9696
Later issues should extend these fixtures instead of creating divergent local terminology. #1732 adds read-only intake for baseline/fork/lineage capability summaries without per-space inference, #1735 owns lifecycle dry-run/safety wiring, and #1736 owns fork/cross-space read-only dashboard views.

packages/fdm-api/src/__tests__/fdmDashboardValidators.test.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import {
22
assertFdmDashboardResponse,
33
type FdmDashboardResponse,
4+
type FdmWorkflowNextAction,
5+
type FdmWorkflowOperationOutcome,
6+
type FdmWorkflowOperationStatus,
7+
type FdmWorkflowStatus,
48
filterFdmCells,
59
normalizeFdmDashboardResponse,
610
projectFdmCellAxisKey,
@@ -277,6 +281,98 @@ describe('fdm dashboard validators', () => {
277281
).not.toThrow();
278282
});
279283

284+
it('accepts every upstream workflow projection enum value explicitly', () => {
285+
const workflowStatuses: readonly FdmWorkflowStatus[] = [
286+
'CREATED',
287+
'PREFLIGHT_FAILED',
288+
'RUNNING',
289+
'DIAGNOSING',
290+
'WAITING_FOR_AGENT',
291+
'RECHECKING_REPAIR',
292+
'WAITING_FOR_HUMAN',
293+
'COMPLETED',
294+
'COMPLETED_WITH_WAIVER',
295+
'ABORTED',
296+
'CANCELLED',
297+
'VERIFYING_REPAIR',
298+
'COMPLETE',
299+
];
300+
const operationStatuses: readonly FdmWorkflowOperationStatus[] = [
301+
'PENDING',
302+
'READY',
303+
'RUNNING',
304+
'TERMINAL',
305+
'UNKNOWN_AFTER_INTERRUPTION',
306+
];
307+
const operationOutcomes: readonly FdmWorkflowOperationOutcome[] = [
308+
'SUCCEEDED',
309+
'DRIFTED',
310+
'MISSING_ARTIFACT',
311+
'UNSUPPORTED_CAPABILITY',
312+
'EXECUTION_FAILED',
313+
'CANCELLED',
314+
'SKIPPED',
315+
];
316+
const nextActions: readonly FdmWorkflowNextAction[] = [
317+
'await-dependency',
318+
'complete-workflow',
319+
'execute-operation',
320+
'request-agent-work',
321+
'rerun-producer',
322+
'run-diagnosis',
323+
'retry-operation',
324+
];
325+
326+
for (const status of workflowStatuses) {
327+
expect(() =>
328+
assertFdmDashboardResponse({
329+
...response,
330+
workflow: {
331+
availability: 'available',
332+
status,
333+
},
334+
})
335+
).not.toThrow();
336+
}
337+
338+
for (const status of operationStatuses) {
339+
expect(() =>
340+
assertFdmDashboardResponse({
341+
...response,
342+
workflow: {
343+
availability: 'available',
344+
operations: [{ id: `operation-${status}`, status }],
345+
},
346+
})
347+
).not.toThrow();
348+
}
349+
350+
for (const outcome of operationOutcomes) {
351+
expect(() =>
352+
assertFdmDashboardResponse({
353+
...response,
354+
workflow: {
355+
availability: 'available',
356+
operations: [{ id: `operation-${outcome}`, status: 'TERMINAL', outcome }],
357+
},
358+
})
359+
).not.toThrow();
360+
}
361+
362+
for (const nextAction of nextActions) {
363+
expect(() =>
364+
assertFdmDashboardResponse({
365+
...response,
366+
workflow: {
367+
availability: 'available',
368+
nextAction,
369+
operations: [{ id: `operation-${nextAction}`, status: 'READY', nextAction }],
370+
},
371+
})
372+
).not.toThrow();
373+
}
374+
});
375+
280376
it('rejects unknown workflow projection enum values', () => {
281377
expect(() =>
282378
assertFdmDashboardResponse({

plugins/fdm-plugin/src/ui/dashboard/FdmDashboardPresentation.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,12 @@ function ProjectionStatusBar({
252252
{ruleset.version ? (
253253
<Chip size="small" label={ruleset.version} variant="outlined" />
254254
) : null}
255+
{ruleset.fingerprint ? (
256+
<Chip size="small" label={ruleset.fingerprint} variant="outlined" />
257+
) : null}
258+
{ruleset.digest ? (
259+
<Chip size="small" label={ruleset.digest} variant="outlined" />
260+
) : null}
255261
</Stack>
256262
{ruleset.message ? (
257263
<Typography variant="caption" color="text.secondary">

plugins/fdm-plugin/src/ui/dashboard/__tests__/FdmDashboardView.test.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@ describe('FdmDashboardView', () => {
273273
ruleset: {
274274
availability: 'stale',
275275
rulesetId: 'ruleset-a',
276+
fingerprint: 'fingerprint-a',
277+
digest: 'sha256:ruleset-a',
276278
acceptedKnownIssues: [{ reason: 'Missing server issue number' }],
277279
message: 'Ruleset requires server-side revalidation',
278280
},
@@ -289,6 +291,65 @@ describe('FdmDashboardView', () => {
289291
).toBeInTheDocument();
290292
expect(screen.getByText('Ruleset')).toBeInTheDocument();
291293
expect(screen.getByText('ruleset-a')).toBeInTheDocument();
294+
expect(screen.getByText('fingerprint-a')).toBeInTheDocument();
295+
expect(screen.getByText('sha256:ruleset-a')).toBeInTheDocument();
296+
expect(screen.getByText(/accepted known issue: unavailable/)).toBeInTheDocument();
297+
});
298+
299+
it.each([
300+
['WAITING_FOR_AGENT', 'request-agent-work', 'Agent work is required'],
301+
['RECHECKING_REPAIR', 'retry-operation', 'Repair recheck is running'],
302+
['COMPLETED_WITH_WAIVER', 'complete-workflow', 'Workflow completed with a server waiver'],
303+
] as const)(
304+
'renders the server-provided workflow state %s',
305+
async (status, nextAction, message) => {
306+
const port: FdmDashboardPort = {
307+
loadDashboard: vi.fn().mockResolvedValue({
308+
...response,
309+
workflow: {
310+
availability: 'available',
311+
workflowId: `workflow-${status}`,
312+
status,
313+
nextAction,
314+
message,
315+
},
316+
}),
317+
performAction: vi.fn(),
318+
};
319+
320+
render(<FdmDashboardView node={response.node} port={port} />);
321+
322+
expect(await screen.findByText(status)).toBeInTheDocument();
323+
expect(screen.getByText(nextAction)).toBeInTheDocument();
324+
expect(screen.getByText(message)).toBeInTheDocument();
325+
}
326+
);
327+
328+
it('renders unavailable and unsupported projection states as server contract messages', async () => {
329+
const port: FdmDashboardPort = {
330+
loadDashboard: vi.fn().mockResolvedValue({
331+
...response,
332+
workflow: {
333+
availability: 'unsupported',
334+
message: 'Workflow projection is not exposed by this server',
335+
},
336+
ruleset: {
337+
availability: 'unavailable',
338+
message: 'Ruleset governance metadata is unavailable',
339+
acceptedKnownIssues: [{ reason: 'Server did not provide an issue number' }],
340+
},
341+
}),
342+
performAction: vi.fn(),
343+
};
344+
345+
render(<FdmDashboardView node={response.node} port={port} />);
346+
347+
expect(await screen.findByText('unsupported')).toBeInTheDocument();
348+
expect(
349+
screen.getByText('Workflow projection is not exposed by this server')
350+
).toBeInTheDocument();
351+
expect(screen.getByText('unavailable')).toBeInTheDocument();
352+
expect(screen.getByText('Ruleset governance metadata is unavailable')).toBeInTheDocument();
292353
expect(screen.getByText(/accepted known issue: unavailable/)).toBeInTheDocument();
293354
});
294355

0 commit comments

Comments
 (0)