Skip to content

Commit 47b0910

Browse files
committed
feat(agent-workspace): add ingest guardrail capability lane
1 parent 474259f commit 47b0910

14 files changed

Lines changed: 234 additions & 1 deletion

docs/brainstorms/2026-04-14-mainline-reality-reconciliation-and-next-direction-requirements.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,18 @@ npm run verify:agent-workspace:tauri
243243
- frontend payload-builder query endpoint/body assertions,
244244
- runtime endpoint/payload/message assertions for query-trace execution.
245245

246+
#### M6.10 Progress Note (2026-04-14)
247+
248+
- [Done] typed conversation capability surface expanded with `inspect_ingest_guardrails` action, `evaluate_ingest_guardrails` operation, and `ingest_guardrail_card` presentation.
249+
- [Done] conversation capability emission now includes ingest-governance guardrail evaluation execution through `/api/knowledge/ingest/guardrails/evaluate`.
250+
- [Done] contract/runtime planners preserve diagnostics payload shaping:
251+
- explicit endpoint binding,
252+
- side-effect-free empty payload.
253+
- [Done] regression coverage expanded for:
254+
- ingest-guardrail capability wiring in conversation output,
255+
- frontend payload-builder ingest-guardrail endpoint/body assertions,
256+
- runtime endpoint/payload/message assertions for ingest-guardrail execution.
257+
246258
### M7 (Future): Foundation Lane Re-entry (Evidence-First)
247259

248260
Entry criteria:

docs/diataxis/en/explanation/development-progress-dashboard.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,21 @@ Execution anchor:
218218
- frontend query payload-builder endpoint/body assertions,
219219
- runtime endpoint/payload/message assertions for query-trace execution and rendering.
220220

221+
## Latest Mainline Increment (2026-04-14 M6.10 Ingest Guardrail Capability Lane)
222+
223+
- Added `inspect_ingest_guardrails` to the typed conversation action union on mainline.
224+
- Extended capability operation/result unions with:
225+
- `evaluate_ingest_guardrails`,
226+
- `ingest_guardrail_card`.
227+
- Extended `runAgentConversation(...)` capability emission to include ingest-guardrail evaluation through `/api/knowledge/ingest/guardrails/evaluate`.
228+
- Kept contract/runtime planner behavior aligned for diagnostics execution:
229+
- explicit guardrail endpoint binding,
230+
- side-effect-free empty payload.
231+
- Expanded regression coverage for:
232+
- ingest-guardrail capability wiring in conversation output,
233+
- frontend ingest-guardrail payload-builder endpoint/body assertions,
234+
- runtime endpoint/payload/message assertions for ingest-guardrail execution and rendering.
235+
221236
## Mainline vs Working-Branch Snapshot (2026-04-14)
222237

223238
| Capability Slice | Working Branch (`feat/learning-multi-tutor-adapter`) | Mainline (`origin/main`) | Integration Status |

docs/diataxis/zh/explanation/development-progress-dashboard.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,21 @@
218218
- 前端 query payload-builder 的 endpoint/body 断言,
219219
- runtime query-trace 路径 endpoint/payload/消息断言。
220220

221+
## 主线最新增量(2026-04-14 M6.10 摄取护栏能力链路)
222+
223+
- 主线 typed conversation action union 新增 `inspect_ingest_guardrails`
224+
- capability operation/result union 同步扩展:
225+
- `evaluate_ingest_guardrails`
226+
- `ingest_guardrail_card`
227+
- `runAgentConversation(...)` capability 输出新增摄取护栏评估动作,执行路径对接 `/api/knowledge/ingest/guardrails/evaluate`
228+
- contract/runtime payload 规划保持对齐:
229+
- endpoint 明确绑定,
230+
- 空 payload 无副作用执行。
231+
- 回归覆盖同步扩展:
232+
- conversation 输出中的 ingest-guardrail capability 连线断言,
233+
- 前端 ingest-guardrail payload-builder 的 endpoint/body 断言,
234+
- runtime ingest-guardrail 路径 endpoint/payload/消息断言。
235+
221236
## 主线 vs 工作分支快照(2026-04-14)
222237

223238
| 能力切片 | 工作分支(`feat/learning-multi-tutor-adapter`| 主线(`origin/main`| 集成状态 |

src/agent_workspace.contract.parity.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ describe('agent_workspace contract parity', () => {
1414
'execute_tutor_action',
1515
'build_study_session',
1616
'query_knowledge',
17+
'evaluate_ingest_guardrails',
1718
'query_session_history',
1819
'query_mastery_misconceptions',
1920
'capture_learning_quality_snapshot',
@@ -31,6 +32,7 @@ describe('agent_workspace contract parity', () => {
3132
'tutor_action_card',
3233
'study_session_card',
3334
'query_trace_card',
35+
'ingest_guardrail_card',
3436
'session_history_card',
3537
'mastery_misconceptions_card',
3638
'learning_quality_snapshot_card',

src/agent_workspace.frontend.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,26 @@ describe('agent_workspace frontend operation contract', () => {
212212
});
213213
});
214214

215+
test('evaluate_ingest_guardrails builds diagnostics payload', () => {
216+
const requestPlan = buildKnowledgeOperationRequestPayload({
217+
request: {
218+
userId: 'learner-a',
219+
atomId: 'atom-2',
220+
},
221+
execution: {
222+
operationId: 'evaluate_ingest_guardrails',
223+
},
224+
});
225+
226+
expect(requestPlan).toEqual({
227+
operationId: 'evaluate_ingest_guardrails',
228+
endpoint: '/api/knowledge/ingest/guardrails/evaluate',
229+
method: 'POST',
230+
resultPresentation: 'ingest_guardrail_card',
231+
body: {},
232+
});
233+
});
234+
215235
test('query_session_history builds bounded history payload', () => {
216236
const requestPlan = buildKnowledgeOperationRequestPayload({
217237
request: {
@@ -395,6 +415,7 @@ describe('agent_workspace frontend operation contract', () => {
395415
execute_tutor_action: 'assistant_message',
396416
build_study_session: 'study_session_card',
397417
query_knowledge: 'query_trace_card',
418+
evaluate_ingest_guardrails: 'ingest_guardrail_card',
398419
query_session_history: 'session_history_card',
399420
query_mastery_misconceptions: 'mastery_misconceptions_card',
400421
capture_learning_quality_snapshot: 'learning_quality_snapshot_card',
@@ -409,6 +430,9 @@ describe('agent_workspace frontend operation contract', () => {
409430
expect(diagnostics.operationAllowedResultPresentations.query_knowledge).toEqual(
410431
expect.arrayContaining(['query_trace_card'])
411432
);
433+
expect(diagnostics.operationAllowedResultPresentations.evaluate_ingest_guardrails).toEqual(
434+
expect.arrayContaining(['ingest_guardrail_card'])
435+
);
412436
expect(diagnostics.operationAllowedResultPresentations.query_session_history).toEqual(
413437
expect.arrayContaining(['session_history_card'])
414438
);

src/agent_workspace.runtime.behavior.test.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ describe('agent workspace runtime behavior', () => {
8484
? '/api/knowledge/session/plan'
8585
: operationId === 'query_knowledge'
8686
? '/api/knowledge/query'
87+
: operationId === 'evaluate_ingest_guardrails'
88+
? '/api/knowledge/ingest/guardrails/evaluate'
8789
: operationId === 'query_session_history'
8890
? '/api/knowledge/session/history'
8991
: operationId === 'query_mastery_misconceptions'
@@ -125,6 +127,15 @@ describe('agent workspace runtime behavior', () => {
125127
},
126128
};
127129
}
130+
if (operationId === 'evaluate_ingest_guardrails') {
131+
return {
132+
operationId,
133+
endpoint,
134+
method: 'POST',
135+
resultPresentation: capability.execution.resultPresentation,
136+
body: {},
137+
};
138+
}
128139
if (operationId === 'query_mastery_misconceptions') {
129140
return {
130141
operationId,
@@ -473,6 +484,85 @@ describe('agent workspace runtime behavior', () => {
473484
expect(messages.textContent || '').toContain('Query trace loaded');
474485
});
475486

487+
test('executes ingest guardrail capability and renders gate summary', async () => {
488+
const fetchMock = jest
489+
.fn()
490+
.mockResolvedValueOnce({
491+
ok: true,
492+
status: 200,
493+
json: async () => ({
494+
success: true,
495+
result: {
496+
userId: 'agent_user_default',
497+
message: 'Found 1 local knowledge point(s).',
498+
knowledgePoints: [
499+
{
500+
atomId: 'atom-guardrail-1',
501+
title: 'Guardrail Candidate',
502+
snippet: 'Inspect ingest governance budget.',
503+
score: 0.71,
504+
capabilities: [
505+
{
506+
actionId: 'inspect_ingest_guardrails',
507+
label: 'Ingest Guardrails',
508+
request: {
509+
userId: 'agent_user_default',
510+
atomId: 'atom-guardrail-1',
511+
},
512+
execution: {
513+
kind: 'knowledge_operation',
514+
operationId: 'evaluate_ingest_guardrails',
515+
resultPresentation: 'ingest_guardrail_card',
516+
},
517+
},
518+
],
519+
},
520+
],
521+
},
522+
}),
523+
})
524+
.mockResolvedValueOnce({
525+
ok: true,
526+
status: 200,
527+
json: async () => ({
528+
success: true,
529+
result: {
530+
overallPassed: false,
531+
gates: [
532+
{ gateId: 'changed_documents', passed: true },
533+
{ gateId: 'ingest_p95', passed: false },
534+
],
535+
},
536+
}),
537+
});
538+
(global as unknown as Record<string, unknown>).fetch = fetchMock;
539+
540+
const runtime = runtimeModule.createAgentWorkspaceRuntime({ defaultUserId: 'agent_user_default' });
541+
runtime.init();
542+
543+
const input = document.getElementById('agent-workspace-input') as HTMLTextAreaElement;
544+
const form = document.getElementById('agent-workspace-form') as HTMLFormElement;
545+
input.value = 'inspect ingest guardrails';
546+
form.dispatchEvent(new dom!.window.Event('submit', { bubbles: true, cancelable: true }));
547+
await flushAsync();
548+
549+
const actionButton = document.querySelector('.agent-workspace-action-button') as HTMLButtonElement;
550+
expect(actionButton).not.toBeNull();
551+
actionButton.click();
552+
await flushAsync();
553+
554+
expect(fetchMock).toHaveBeenNthCalledWith(
555+
2,
556+
'/api/knowledge/ingest/guardrails/evaluate',
557+
expect.objectContaining({ method: 'POST' })
558+
);
559+
const payload = JSON.parse(String(fetchMock.mock.calls[1]?.[1]?.body || '{}')) as Record<string, unknown>;
560+
expect(payload).toEqual({});
561+
562+
const messages = document.getElementById('agent-workspace-messages') as HTMLElement;
563+
expect(messages.textContent || '').toContain('Ingest guardrails evaluated');
564+
});
565+
476566
test('executes mastery misconceptions capability and renders summary message', async () => {
477567
const fetchMock = jest
478568
.fn()

src/agent_workspace.runtime.integration.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ describe('agent workspace runtime integration baseline', () => {
2626
expect(runtimeSource).toContain('/api/knowledge/conversation');
2727
expect(runtimeSource).toContain('/api/knowledge/session/plan');
2828
expect(runtimeSource).toContain('/api/knowledge/query');
29+
expect(runtimeSource).toContain('/api/knowledge/ingest/guardrails/evaluate');
2930
expect(runtimeSource).toContain('/api/knowledge/session/history');
3031
expect(runtimeSource).toContain('/api/knowledge/mastery/misconceptions');
3132
expect(runtimeSource).toContain('/api/knowledge/quality/snapshot');

src/frontend/agent_workspace.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@
3838
defaultResultPresentation: 'query_trace_card',
3939
allowedResultPresentations: Object.freeze(['query_trace_card']),
4040
}),
41+
evaluate_ingest_guardrails: Object.freeze({
42+
endpoint: '/api/knowledge/ingest/guardrails/evaluate',
43+
method: 'POST',
44+
defaultResultPresentation: 'ingest_guardrail_card',
45+
allowedResultPresentations: Object.freeze(['ingest_guardrail_card']),
46+
}),
4147
query_session_history: Object.freeze({
4248
endpoint: '/api/knowledge/session/history',
4349
method: 'POST',
@@ -289,6 +295,16 @@
289295
};
290296
}
291297

298+
if (operationId === 'evaluate_ingest_guardrails') {
299+
return {
300+
operationId,
301+
endpoint: config.endpoint,
302+
method: config.method,
303+
resultPresentation,
304+
body: {},
305+
};
306+
}
307+
292308
if (operationId === 'query_session_history') {
293309
const userId = String(request.userId || '').trim();
294310
if (!userId) {

src/frontend/agent_workspace_runtime.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,15 @@
235235
},
236236
};
237237
}
238+
if (operationId === 'evaluate_ingest_guardrails') {
239+
return {
240+
operationId,
241+
endpoint: '/api/knowledge/ingest/guardrails/evaluate',
242+
method: 'POST',
243+
resultPresentation: trimString(execution.resultPresentation) || 'ingest_guardrail_card',
244+
body: {},
245+
};
246+
}
238247
if (operationId === 'query_session_history') {
239248
return {
240249
operationId,
@@ -634,6 +643,26 @@
634643
return;
635644
}
636645

646+
if (presentation === 'ingest_guardrail_card') {
647+
const gates = Array.isArray(result && result.gates) ? result.gates : [];
648+
const totalGates = gates.length;
649+
const failedGates = gates.filter((gate) => gate && gate.passed === false).length;
650+
const overallPassed = Boolean(result && result.overallPassed);
651+
appendMessage(
652+
'assistant',
653+
getI18nText(
654+
'agentWorkspace.messages.ingestGuardrailLoaded',
655+
`Ingest guardrails evaluated (${overallPassed ? 'pass' : 'fail'}): ${failedGates}/${totalGates} gates failed.`,
656+
{
657+
status: overallPassed ? 'pass' : 'fail',
658+
failedGates,
659+
totalGates,
660+
}
661+
)
662+
);
663+
return;
664+
}
665+
637666
if (presentation === 'session_history_card') {
638667
const summary = result && result.summary && typeof result.summary === 'object'
639668
? result.summary

src/frontend/locales/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@
241241
"generateCounterexample": "Generate Counterexample",
242242
"buildStudySession": "Build Session",
243243
"inspectQueryTrace": "Query Trace",
244+
"inspectIngestGuardrails": "Ingest Guardrails",
244245
"inspectSessionHistory": "Session History",
245246
"inspectMasteryMisconceptions": "Mastery Misconceptions",
246247
"inspectLearningQualitySnapshot": "Learning Quality",
@@ -270,6 +271,8 @@
270271
"studySessionBuilt": "Study session built ({totalActions} actions, {totalEstimatedMinutes} min).",
271272
"queryTraceFailed": "Query trace request failed.",
272273
"queryTraceLoaded": "Query trace loaded ({itemsCount} hits, modes {retrievalModes}, latency {latencyMs} ms, evidence {evidenceCoverageRatio}).",
274+
"ingestGuardrailFailed": "Ingest guardrail evaluation failed.",
275+
"ingestGuardrailLoaded": "Ingest guardrails evaluated ({status}): {failedGates}/{totalGates} gates failed.",
273276
"sessionHistoryFailed": "Session history request failed.",
274277
"sessionHistoryLoaded": "Session history loaded ({totalRecords} sessions, {totalExecutedActions} actions).",
275278
"masteryMisconceptionsFailed": "Mastery misconceptions request failed.",

0 commit comments

Comments
 (0)