@swarmclawai/swarmclaw 1.9.11 → 1.9.13
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/README.md +19 -1
- package/package.json +3 -2
- package/src/app/api/quality/architecture-health/route.ts +16 -0
- package/src/app/api/quality/release-readiness/route.ts +6 -1
- package/src/app/home/page.tsx +1 -1
- package/src/cli/index.js +1 -0
- package/src/cli/index.ts +1 -1
- package/src/components/connectors/connector-sheet.tsx +36 -5
- package/src/components/quality/quality-workspace.tsx +155 -1
- package/src/components/shared/command-palette.tsx +1 -1
- package/src/components/shared/connector-platform-icon.test.ts +4 -0
- package/src/components/shared/connector-platform-icon.tsx +1 -0
- package/src/lib/connectors/connector-readiness.ts +17 -7
- package/src/lib/quality/architecture-health.test.ts +79 -0
- package/src/lib/quality/architecture-health.ts +451 -0
- package/src/lib/quality/release-readiness.test.ts +13 -0
- package/src/lib/quality/release-readiness.ts +36 -0
- package/src/lib/server/connectors/connector-lifecycle.ts +2 -1
- package/src/lib/server/connectors/connector-routing.test.ts +1 -1
- package/src/lib/server/connectors/connector-service.ts +1 -0
- package/src/lib/server/connectors/email.test.ts +1 -0
- package/src/lib/server/connectors/filequeue.test.ts +141 -0
- package/src/lib/server/connectors/filequeue.ts +324 -0
- package/src/lib/server/session-tools/crud.ts +1 -0
- package/src/lib/server/storage.ts +1 -1
- package/src/lib/validation/schemas.ts +1 -1
- package/src/types/connector.ts +1 -0
package/README.md
CHANGED
|
@@ -183,7 +183,7 @@ Full hosted deployment guides live at https://swarmclaw.ai/docs/deployment
|
|
|
183
183
|
- **Structured Sessions**: reusable bounded runs with templates, facilitators, participants, hidden live rooms, chatroom `/breakout`, durable transcripts, outputs, operator controls, and a visible protocols template gallery plus visual builder.
|
|
184
184
|
- **Memory**: hybrid recall, graph traversal, journaling, durable documents, project-scoped context, automatic reflection memory, communication preferences, profile and boundary memory, significant events, and open follow-up loops.
|
|
185
185
|
- **Wallets**: linked Base wallet generation, address management, approval-oriented limits, and agent payout identity.
|
|
186
|
-
- **Connectors**: Discord, Slack, Telegram, WhatsApp, Teams, Matrix, OpenClaw, SwarmDock, SwarmFeed, and more.
|
|
186
|
+
- **Connectors**: Discord, Slack, Telegram, WhatsApp, Teams, Matrix, email, local file queues, OpenClaw, SwarmDock, SwarmFeed, and more.
|
|
187
187
|
- **MCP Servers**: connect any Model Context Protocol server (stdio, SSE, or streamable HTTP) and inject its tools into agents alongside built-ins. Configure, test, and assign per-agent from the MCP Servers panel.
|
|
188
188
|
- **Extensions**: external tool extensions, UI modules, hooks, install/update flows, and managed resource manifests for extension-owned agents, routines, local folders, gateways, and setup checks.
|
|
189
189
|
|
|
@@ -399,6 +399,24 @@ Operational docs: https://swarmclaw.ai/docs/observability
|
|
|
399
399
|
|
|
400
400
|
## Releases
|
|
401
401
|
|
|
402
|
+
### v1.9.13 Highlights
|
|
403
|
+
|
|
404
|
+
Architecture health release: SwarmClaw now turns runtime ownership, dispatch, memory, startup, and quality evidence into a scored operator report.
|
|
405
|
+
|
|
406
|
+
- **Architecture Health report.** `/api/quality/architecture-health` returns a structured inventory of runtime domains, surfaces, owners, guardrails, tests, score, risks, warnings, and next actions.
|
|
407
|
+
- **Quality Center visibility.** `/quality` now shows a Runtime Ownership Map beside release readiness so operators can inspect dispatch, memory, startup, and quality coverage before shipping.
|
|
408
|
+
- **Release gate integration.** Release readiness includes architecture health when scoring the ship gate report, blocking or warning when ownership evidence is incomplete.
|
|
409
|
+
- **CLI access.** `swarmclaw operations architecture-health` exposes the same report for automation and release scripts.
|
|
410
|
+
|
|
411
|
+
### v1.9.12 Highlights
|
|
412
|
+
|
|
413
|
+
Local file-queue connector release: operators can bridge SwarmClaw to filesystem inbox, outbox, archive, and error folders without a hosted message bus.
|
|
414
|
+
|
|
415
|
+
- **File Queue connector.** Configure root, inbox, outbox, archive, and error folders from the connector sheet or CLI.
|
|
416
|
+
- **JSON command ingress.** External tools can drop command envelopes into the inbox, then SwarmClaw normalizes them into connector messages for the selected agent or chatroom.
|
|
417
|
+
- **Durable file handling.** Processed commands move to archive, malformed commands move to errors with diagnostic sidecars, and replies are written to outbox as structured JSON.
|
|
418
|
+
- **Connector runtime parity.** Queue traffic uses the existing connector session, policy, health, readiness, CLI, and follow-up delivery paths.
|
|
419
|
+
|
|
402
420
|
### v1.9.11 Highlights
|
|
403
421
|
|
|
404
422
|
Task execution policy release: operators can attach ordered review, approval, and verification stages to board tasks, record decisions, and block premature completion until required stages clear.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.13",
|
|
4
4
|
"description": "Build and run autonomous AI agents with OpenClaw, Hermes, multiple model providers, orchestration, delegation, memory, skills, schedules, and chat connectors.",
|
|
5
5
|
"main": "electron-dist/main.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"clawd",
|
|
30
30
|
"clawdbot",
|
|
31
31
|
"moltbot",
|
|
32
|
+
"file-queue",
|
|
32
33
|
"openclaw-skill",
|
|
33
34
|
"openclaw-dashboard",
|
|
34
35
|
"openclaw-gateway",
|
|
@@ -87,7 +88,7 @@
|
|
|
87
88
|
"test:cli": "node --test src/cli/*.test.js bin/*.test.js scripts/electron-after-pack.test.mjs scripts/ensure-sandbox-browser-image.test.mjs scripts/postinstall.test.mjs scripts/run-next-build.test.mjs scripts/run-next-typegen.test.mjs",
|
|
88
89
|
"test:setup": "tsx --test src/app/api/setup/check-provider/route.test.ts src/lib/server/provider-model-discovery.test.ts src/components/auth/setup-wizard/utils.test.ts src/components/auth/setup-wizard/types.test.ts src/hooks/setup-done-detection.test.ts src/lib/setup-defaults.test.ts src/lib/server/storage-auth.test.ts src/lib/server/storage-auth-docker.test.ts",
|
|
89
90
|
"test:openclaw": "tsx --test src/lib/openclaw/openclaw-agent-id.test.ts src/lib/openclaw/openclaw-endpoint.test.ts src/lib/server/agents/agent-runtime-config.test.ts src/lib/server/build-llm.test.ts src/lib/server/connectors/connector-routing.test.ts src/lib/server/connectors/openclaw.test.ts src/lib/server/connectors/swarmdock.test.ts src/lib/server/gateway/protocol.test.ts src/lib/server/gateways/gateway-topology.test.ts src/lib/server/llm-response-cache.test.ts src/lib/server/mcp-conformance.test.ts src/lib/server/openclaw/agent-resolver.test.ts src/lib/server/openclaw/deploy.test.ts src/lib/server/openclaw/skills-normalize.test.ts src/lib/server/session-tools/openclaw-nodes.test.ts src/lib/server/session-tools/swarmdock.test.ts src/lib/server/tasks/task-quality-gate.test.ts src/lib/server/tasks/task-validation.test.ts src/lib/server/tool-capability-policy.test.ts src/lib/providers/openai.test.ts src/lib/providers/openclaw-exports.test.ts src/app/api/gateways/topology-route.test.ts src/app/api/openclaw/dashboard-url/route.test.ts",
|
|
90
|
-
"test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/strip-internal-metadata.test.ts src/lib/provider-sets.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-provider-metadata.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/agents/delegation-advisory.test.ts src/lib/server/cli-provider-readiness.test.ts src/lib/server/provider-health.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/extension-managed-resources.test.ts src/lib/server/eval/baseline.test.ts src/lib/server/eval/environment-plan.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chat-execution/reasoning-tag-scrubber.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/connectors/email.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/runtime/queue-retry-policy.test.ts src/lib/server/runs/run-brief.test.ts src/lib/server/operations/operation-pulse.test.ts src/lib/server/schedules/schedule-history.test.ts src/lib/quality/release-readiness.test.ts src/lib/server/artifacts/artifact-resolver.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/server/sharing/share-link-repository.test.ts src/lib/server/sharing/share-resolver.test.ts src/lib/server/tasks/task-execution-workspace.test.ts src/lib/server/tasks/task-execution-policy.test.ts src/lib/server/tasks/task-handoff.test.ts src/lib/server/tasks/task-service.test.ts src/lib/server/session-tools/execute.test.ts src/lib/server/session-tools/manage-tasks.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/tasks/task-workspace-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/extensions/managed-resources/route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/portability/export/route.test.ts src/app/api/portability/import/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/schedules/schedule-history-route.test.ts src/app/api/tts/route.test.ts",
|
|
91
|
+
"test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/strip-internal-metadata.test.ts src/lib/provider-sets.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-provider-metadata.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/agents/delegation-advisory.test.ts src/lib/server/cli-provider-readiness.test.ts src/lib/server/provider-health.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/extension-managed-resources.test.ts src/lib/server/eval/baseline.test.ts src/lib/server/eval/environment-plan.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chat-execution/reasoning-tag-scrubber.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/connectors/email.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/runtime/queue-retry-policy.test.ts src/lib/server/runs/run-brief.test.ts src/lib/server/operations/operation-pulse.test.ts src/lib/server/schedules/schedule-history.test.ts src/lib/quality/release-readiness.test.ts src/lib/quality/architecture-health.test.ts src/lib/server/artifacts/artifact-resolver.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/server/sharing/share-link-repository.test.ts src/lib/server/sharing/share-resolver.test.ts src/lib/server/tasks/task-execution-workspace.test.ts src/lib/server/tasks/task-execution-policy.test.ts src/lib/server/tasks/task-handoff.test.ts src/lib/server/tasks/task-service.test.ts src/lib/server/session-tools/execute.test.ts src/lib/server/session-tools/manage-tasks.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/tasks/task-workspace-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/extensions/managed-resources/route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/portability/export/route.test.ts src/app/api/portability/import/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/schedules/schedule-history-route.test.ts src/app/api/tts/route.test.ts",
|
|
91
92
|
"test:builder": "tsx --test src/features/protocols/builder/utils/nodes-to-template.test.ts src/features/protocols/builder/utils/template-to-nodes.test.ts src/features/protocols/builder/validators/dag-validator.test.ts",
|
|
92
93
|
"test:e2e": "node --import tsx scripts/browser-e2e-smoke.ts",
|
|
93
94
|
"test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { buildArchitectureHealthReport } from '@/lib/quality/architecture-health'
|
|
3
|
+
import { errorMessage } from '@/lib/shared-utils'
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-dynamic'
|
|
6
|
+
|
|
7
|
+
export async function GET() {
|
|
8
|
+
try {
|
|
9
|
+
return NextResponse.json(buildArchitectureHealthReport())
|
|
10
|
+
} catch (err: unknown) {
|
|
11
|
+
return NextResponse.json(
|
|
12
|
+
{ error: errorMessage(err) },
|
|
13
|
+
{ status: 500 },
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { evaluateEvalGate } from '@/lib/server/eval/baseline'
|
|
3
3
|
import { getOperationPulse, normalizeOperationPulseRange } from '@/lib/server/operations/operation-pulse'
|
|
4
|
+
import { buildArchitectureHealthReport } from '@/lib/quality/architecture-health'
|
|
4
5
|
import { buildReleaseReadinessReport } from '@/lib/quality/release-readiness'
|
|
5
6
|
import { errorMessage } from '@/lib/shared-utils'
|
|
6
7
|
|
|
@@ -28,7 +29,11 @@ export async function GET(req: Request) {
|
|
|
28
29
|
})
|
|
29
30
|
: null
|
|
30
31
|
|
|
31
|
-
return NextResponse.json(buildReleaseReadinessReport({
|
|
32
|
+
return NextResponse.json(buildReleaseReadinessReport({
|
|
33
|
+
pulse,
|
|
34
|
+
evalGate,
|
|
35
|
+
architectureHealth: buildArchitectureHealthReport(),
|
|
36
|
+
}))
|
|
32
37
|
} catch (err: unknown) {
|
|
33
38
|
return NextResponse.json(
|
|
34
39
|
{ error: errorMessage(err) },
|
package/src/app/home/page.tsx
CHANGED
|
@@ -429,7 +429,7 @@ export default function HomePage() {
|
|
|
429
429
|
<StatCard label="Agents" value={String(agentCount)} hint="Total active agents configured in your dashboard" index={0} />
|
|
430
430
|
<StatCard label="Active Tasks" value={String(activeTaskCount)} accent={activeTaskCount > 0} hint="Tasks currently running or queued for execution" index={1} />
|
|
431
431
|
<StatCard label="Today's Cost" value={`$${todayCost.toFixed(2)}`} hint="Estimated API cost for today across all providers" index={2} />
|
|
432
|
-
<StatCard label="Connectors" value={`${activeConnectorCount}/${allConnectors.length}`} accent={activeConnectorCount > 0} hint="Active bridges to chat platforms
|
|
432
|
+
<StatCard label="Connectors" value={`${activeConnectorCount}/${allConnectors.length}`} accent={activeConnectorCount > 0} hint="Active bridges to chat platforms, local queues, and agent channels." index={3} />
|
|
433
433
|
</div>
|
|
434
434
|
|
|
435
435
|
{/* Cost trend sparkline */}
|
package/src/cli/index.js
CHANGED
|
@@ -211,6 +211,7 @@ const COMMAND_GROUPS = [
|
|
|
211
211
|
commands: [
|
|
212
212
|
cmd('pulse', 'GET', '/operations/pulse', 'Get Operations Pulse summary (use --query range=24h or --query range=7d)'),
|
|
213
213
|
cmd('readiness', 'GET', '/quality/release-readiness', 'Get release readiness report (use --query agentId=... and --query suite=core for eval gate coverage)'),
|
|
214
|
+
cmd('architecture-health', 'GET', '/quality/architecture-health', 'Get architecture health inventory and drift report'),
|
|
214
215
|
],
|
|
215
216
|
},
|
|
216
217
|
{
|
package/src/cli/index.ts
CHANGED
|
@@ -1177,7 +1177,7 @@ export function buildProgram(): Command {
|
|
|
1177
1177
|
connectors
|
|
1178
1178
|
.command('create')
|
|
1179
1179
|
.description('Create connector')
|
|
1180
|
-
.requiredOption('--platform <platform>', 'Connector platform (discord|telegram|slack|whatsapp|openclaw|bluebubbles|signal|teams|googlechat|matrix)')
|
|
1180
|
+
.requiredOption('--platform <platform>', 'Connector platform (discord|telegram|slack|whatsapp|openclaw|bluebubbles|signal|teams|googlechat|matrix|email|filequeue|swarmdock)')
|
|
1181
1181
|
.requiredOption('--agent-id <agentId>', 'Agent id')
|
|
1182
1182
|
.option('--name <name>', 'Connector name')
|
|
1183
1183
|
.option('--credential-id <credentialId>', 'Credential id')
|
|
@@ -86,6 +86,12 @@ interface ConnectorConfigField {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
const FIELD_HINTS: Record<string, string> = {
|
|
89
|
+
rootDir: 'Root folder for the queue. Defaults to a managed folder under SwarmClaw data.',
|
|
90
|
+
inboxDir: 'Folder where external tools drop inbound JSON commands. Relative paths are resolved under Root Directory.',
|
|
91
|
+
outboxDir: 'Folder where SwarmClaw writes outbound JSON replies. Relative paths are resolved under Root Directory.',
|
|
92
|
+
archiveDir: 'Processed inbound JSON commands are moved here after routing.',
|
|
93
|
+
errorDir: 'Malformed or failed inbound JSON commands are moved here with an error sidecar.',
|
|
94
|
+
pollIntervalMs: 'How often SwarmClaw checks the inbox folder. Minimum 250 ms.',
|
|
89
95
|
channelIds: "Find these in your platform's developer settings. Leave empty to allow all channels",
|
|
90
96
|
chatIds: "Find these in your platform's developer settings. Leave empty to allow all chats",
|
|
91
97
|
roomIds: 'Leave empty to allow all rooms visible to the bot',
|
|
@@ -366,6 +372,30 @@ const PLATFORMS: {
|
|
|
366
372
|
{ key: 'maxBudget', label: 'Max Budget (USDC micro-units)', placeholder: '5000000', help: '$1 = 1000000, $5 = 5000000' },
|
|
367
373
|
],
|
|
368
374
|
},
|
|
375
|
+
{
|
|
376
|
+
id: 'filequeue',
|
|
377
|
+
label: 'File Queue',
|
|
378
|
+
color: '#22C55E',
|
|
379
|
+
setupSteps: [
|
|
380
|
+
'Choose a root directory or let SwarmClaw create a managed one',
|
|
381
|
+
'External tools write JSON commands into the inbox folder',
|
|
382
|
+
'SwarmClaw archives processed commands and writes JSON replies into the outbox folder',
|
|
383
|
+
'Use an agent or chatroom route to decide who handles inbound commands',
|
|
384
|
+
],
|
|
385
|
+
tokenLabel: '',
|
|
386
|
+
tokenHelp: '',
|
|
387
|
+
configFields: [
|
|
388
|
+
{ key: 'rootDir', label: 'Root Directory', placeholder: '~/swarmclaw-command-queue', help: 'Optional. Defaults to data/connectors/<id>/filequeue.', section: 'basic' },
|
|
389
|
+
{ key: 'inboxDir', label: 'Inbox Directory', placeholder: 'inbox', help: 'Inbound JSON command directory. Relative paths resolve under Root Directory.', section: 'basic' },
|
|
390
|
+
{ key: 'outboxDir', label: 'Outbox Directory', placeholder: 'outbox', help: 'Outbound JSON reply directory. Relative paths resolve under Root Directory.', section: 'basic' },
|
|
391
|
+
{ key: 'archiveDir', label: 'Archive Directory', placeholder: 'archive', help: 'Processed command archive directory.', section: 'advanced' },
|
|
392
|
+
{ key: 'errorDir', label: 'Error Directory', placeholder: 'errors', help: 'Malformed command quarantine directory.', section: 'advanced' },
|
|
393
|
+
{ key: 'pollIntervalMs', label: 'Poll Interval (ms)', placeholder: '1000', help: 'How often to scan the inbox. Minimum 250 ms.', section: 'advanced' },
|
|
394
|
+
{ key: 'defaultChannelId', label: 'Default Channel ID', placeholder: 'ops', help: 'Used when an inbound command omits channelId.', section: 'advanced' },
|
|
395
|
+
{ key: 'defaultSenderId', label: 'Default Sender ID', placeholder: 'queue', help: 'Used when an inbound command omits senderId.', section: 'advanced' },
|
|
396
|
+
{ key: 'defaultSenderName', label: 'Default Sender Name', placeholder: 'Queue', help: 'Used when an inbound command omits senderName.', section: 'advanced' },
|
|
397
|
+
],
|
|
398
|
+
},
|
|
369
399
|
]
|
|
370
400
|
|
|
371
401
|
const COMMON_CONFIG_FIELDS: ConnectorConfigField[] = [
|
|
@@ -780,10 +810,10 @@ export function ConnectorSheet() {
|
|
|
780
810
|
await saveConnectorMutation.mutateAsync({
|
|
781
811
|
id: editing?.id,
|
|
782
812
|
payload: {
|
|
783
|
-
name: name || `${platformConfig?.label} Bot
|
|
813
|
+
name: name || (platform === 'filequeue' ? `${platformConfig?.label} Connector` : `${platformConfig?.label} Bot`),
|
|
784
814
|
platform,
|
|
785
815
|
...routePayload,
|
|
786
|
-
credentialId: credentialId || null,
|
|
816
|
+
credentialId: showCredentialSection ? (credentialId || null) : null,
|
|
787
817
|
config,
|
|
788
818
|
},
|
|
789
819
|
})
|
|
@@ -848,6 +878,7 @@ export function ConnectorSheet() {
|
|
|
848
878
|
const credList = Object.values(credentials)
|
|
849
879
|
const basicPlatformFields = platformConfig.configFields.filter((field) => field.section !== 'advanced')
|
|
850
880
|
const advancedPlatformFields = platformConfig.configFields.filter((field) => field.section === 'advanced')
|
|
881
|
+
const showCredentialSection = Boolean(platformConfig.tokenLabel.trim())
|
|
851
882
|
const basicAccessFields = ACCESS_CONTROL_FIELDS.filter((field) => field.section !== 'advanced')
|
|
852
883
|
const advancedAccessFields = ACCESS_CONTROL_FIELDS.filter((field) => field.section === 'advanced')
|
|
853
884
|
const hasConfiguredValue = useCallback((key: string) => Boolean(config[key]?.trim()), [config])
|
|
@@ -1014,7 +1045,7 @@ export function ConnectorSheet() {
|
|
|
1014
1045
|
<div>
|
|
1015
1046
|
<div className={`text-[14px] font-600 ${platform === p.id ? 'text-text' : 'text-text-2'}`}>{p.label}</div>
|
|
1016
1047
|
<div className="text-[11px] text-text-3 mt-0.5">
|
|
1017
|
-
{p.id === 'whatsapp' ? 'QR code pairing' : p.id === 'openclaw' ? 'WebSocket gateway' : p.id === 'bluebubbles' ? 'iMessage bridge' : p.id === 'signal' ? 'signal-cli binary' : p.id === 'matrix' ? 'Access token' : p.id === 'googlechat' ? 'Service account' : p.id === 'teams' ? 'Bot Framework' : 'Bot token'}
|
|
1048
|
+
{p.id === 'whatsapp' ? 'QR code pairing' : p.id === 'openclaw' ? 'WebSocket gateway' : p.id === 'bluebubbles' ? 'iMessage bridge' : p.id === 'signal' ? 'signal-cli binary' : p.id === 'matrix' ? 'Access token' : p.id === 'googlechat' ? 'Service account' : p.id === 'teams' ? 'Bot Framework' : p.id === 'filequeue' ? 'Local queue' : 'Bot token'}
|
|
1018
1049
|
</div>
|
|
1019
1050
|
</div>
|
|
1020
1051
|
</button>
|
|
@@ -1074,7 +1105,7 @@ export function ConnectorSheet() {
|
|
|
1074
1105
|
<input
|
|
1075
1106
|
value={name}
|
|
1076
1107
|
onChange={(e) => setName(e.target.value)}
|
|
1077
|
-
placeholder={`My ${platformConfig.label} Bot`}
|
|
1108
|
+
placeholder={`My ${platformConfig.label} ${platform === 'filequeue' ? 'Connector' : 'Bot'}`}
|
|
1078
1109
|
className={inputClass}
|
|
1079
1110
|
style={{ fontFamily: 'inherit' }}
|
|
1080
1111
|
/>
|
|
@@ -1128,7 +1159,7 @@ export function ConnectorSheet() {
|
|
|
1128
1159
|
</div>
|
|
1129
1160
|
|
|
1130
1161
|
{/* Bot token credential */}
|
|
1131
|
-
{
|
|
1162
|
+
{showCredentialSection && (
|
|
1132
1163
|
<div className="mb-6">
|
|
1133
1164
|
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">{platformConfig.tokenLabel}</label>
|
|
1134
1165
|
<p className="text-[12px] text-text-3/60 mb-2">{platformConfig.tokenHelp}</p>
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
summarizeEvalRuns,
|
|
16
16
|
summarizeRunHealth,
|
|
17
17
|
} from '@/lib/quality/quality-summary'
|
|
18
|
+
import type { ArchitectureHealthReport, ArchitectureHealthStatus } from '@/lib/quality/architecture-health'
|
|
18
19
|
import type { ReleaseReadinessReport, ReleaseReadinessStatus } from '@/lib/quality/release-readiness'
|
|
19
20
|
import { cn } from '@/lib/utils'
|
|
20
21
|
import { useAppStore } from '@/stores/use-app-store'
|
|
@@ -142,6 +143,18 @@ function readinessScoreTone(status: ReleaseReadinessStatus): string {
|
|
|
142
143
|
return 'text-rose-300'
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
function architectureStatusClass(status: ArchitectureHealthStatus): string {
|
|
147
|
+
if (status === 'healthy') return 'border-emerald-500/25 bg-emerald-500/10 text-emerald-200'
|
|
148
|
+
if (status === 'watch') return 'border-amber-500/25 bg-amber-500/10 text-amber-200'
|
|
149
|
+
return 'border-rose-500/25 bg-rose-500/10 text-rose-200'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function architectureScoreTone(status: ArchitectureHealthStatus): string {
|
|
153
|
+
if (status === 'healthy') return 'text-emerald-300'
|
|
154
|
+
if (status === 'watch') return 'text-amber-300'
|
|
155
|
+
return 'text-rose-300'
|
|
156
|
+
}
|
|
157
|
+
|
|
145
158
|
function ReleaseReadinessPanel({
|
|
146
159
|
report,
|
|
147
160
|
loading,
|
|
@@ -254,6 +267,118 @@ function ReleaseReadinessPanel({
|
|
|
254
267
|
)
|
|
255
268
|
}
|
|
256
269
|
|
|
270
|
+
function ArchitectureHealthPanel({
|
|
271
|
+
report,
|
|
272
|
+
loading,
|
|
273
|
+
onRefresh,
|
|
274
|
+
onOpenHref,
|
|
275
|
+
}: {
|
|
276
|
+
report: ArchitectureHealthReport | null
|
|
277
|
+
loading: boolean
|
|
278
|
+
onRefresh: () => void
|
|
279
|
+
onOpenHref: (href: string) => void
|
|
280
|
+
}) {
|
|
281
|
+
return (
|
|
282
|
+
<section className="rounded-[16px] border border-white/[0.06] bg-white/[0.025] p-4">
|
|
283
|
+
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
|
|
284
|
+
<div>
|
|
285
|
+
<div className="text-[11px] font-700 uppercase tracking-[0.12em] text-accent-bright/70">Architecture Health</div>
|
|
286
|
+
<h2 className="mt-1 font-display text-[17px] font-700 text-text">Runtime ownership map</h2>
|
|
287
|
+
<p className="mt-1 max-w-[680px] text-[12px] leading-relaxed text-text-3/65">
|
|
288
|
+
Inventories dispatch, memory, startup, and quality surfaces with owners, guardrails, and test evidence.
|
|
289
|
+
</p>
|
|
290
|
+
</div>
|
|
291
|
+
<button
|
|
292
|
+
type="button"
|
|
293
|
+
onClick={onRefresh}
|
|
294
|
+
disabled={loading}
|
|
295
|
+
className="shrink-0 rounded-[10px] border border-white/[0.08] bg-white/[0.04] px-3 py-2 text-[12px] font-800 text-text-2 transition-colors hover:bg-white/[0.08] disabled:opacity-40"
|
|
296
|
+
>
|
|
297
|
+
{loading ? 'Checking' : 'Refresh map'}
|
|
298
|
+
</button>
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
{!report ? (
|
|
302
|
+
<div className="mt-4 rounded-[12px] border border-dashed border-white/[0.08] bg-white/[0.02] px-4 py-5 text-[12px] text-text-3/65">
|
|
303
|
+
{loading ? 'Building architecture health report...' : 'No architecture health report is available yet.'}
|
|
304
|
+
</div>
|
|
305
|
+
) : (
|
|
306
|
+
<div className="mt-4 grid gap-4 xl:grid-cols-[260px_1fr]">
|
|
307
|
+
<div className="rounded-[14px] border border-white/[0.06] bg-white/[0.025] p-4">
|
|
308
|
+
<span className={cn('inline-flex rounded-full border px-2.5 py-1 text-[10px] font-800 uppercase tracking-[0.1em]', architectureStatusClass(report.status))}>
|
|
309
|
+
{report.status}
|
|
310
|
+
</span>
|
|
311
|
+
<div className={cn('mt-4 font-display text-[42px] font-700 tracking-[-0.04em]', architectureScoreTone(report.status))}>{report.score}</div>
|
|
312
|
+
<div className="mt-1 text-[12px] text-text-3/65">health score</div>
|
|
313
|
+
<div className="mt-4 grid grid-cols-2 gap-2">
|
|
314
|
+
<div className="rounded-[10px] bg-white/[0.035] px-3 py-2">
|
|
315
|
+
<div className="text-[10px] font-700 uppercase tracking-[0.1em] text-text-3/50">Surfaces</div>
|
|
316
|
+
<div className="mt-1 text-[18px] font-800 text-text">{report.surfaceCount}</div>
|
|
317
|
+
</div>
|
|
318
|
+
<div className="rounded-[10px] bg-white/[0.035] px-3 py-2">
|
|
319
|
+
<div className="text-[10px] font-700 uppercase tracking-[0.1em] text-text-3/50">Guardrails</div>
|
|
320
|
+
<div className="mt-1 text-[18px] font-800 text-text">{report.guardrailCount}</div>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
<div className="grid gap-3 lg:grid-cols-2">
|
|
326
|
+
<div className="rounded-[14px] border border-white/[0.06] bg-white/[0.02] p-3">
|
|
327
|
+
<div className="text-[12px] font-800 text-text">Domains</div>
|
|
328
|
+
<div className="mt-3 grid gap-2">
|
|
329
|
+
{report.domains.map((domain) => (
|
|
330
|
+
<div key={domain.id} className="rounded-[10px] border border-white/[0.06] bg-white/[0.025] px-3 py-2">
|
|
331
|
+
<div className="flex items-center justify-between gap-2">
|
|
332
|
+
<div className="text-[12px] font-800 text-text">{domain.title}</div>
|
|
333
|
+
<span className={cn('rounded-full border px-2 py-0.5 text-[9px] font-800 uppercase tracking-[0.08em]', architectureStatusClass(domain.status))}>
|
|
334
|
+
{domain.status}
|
|
335
|
+
</span>
|
|
336
|
+
</div>
|
|
337
|
+
<div className="mt-1 text-[11px] leading-relaxed text-text-3/65">{domain.surfaces.length} surfaces, {domain.testPaths.length} evidence paths</div>
|
|
338
|
+
</div>
|
|
339
|
+
))}
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
|
|
343
|
+
<div className="rounded-[14px] border border-white/[0.06] bg-white/[0.02] p-3">
|
|
344
|
+
<div className="text-[12px] font-800 text-text">Checks</div>
|
|
345
|
+
<div className="mt-3 flex flex-col gap-2">
|
|
346
|
+
{report.nextActions.length === 0 ? (
|
|
347
|
+
report.checks.filter((check) => check.status === 'healthy').slice(0, 4).map((check) => (
|
|
348
|
+
<button
|
|
349
|
+
key={check.code}
|
|
350
|
+
type="button"
|
|
351
|
+
onClick={() => check.href && onOpenHref(check.href)}
|
|
352
|
+
className="rounded-[10px] border border-emerald-500/20 bg-emerald-500/[0.05] px-3 py-2 text-left text-emerald-200 transition-colors hover:bg-emerald-500/[0.08]"
|
|
353
|
+
>
|
|
354
|
+
<div className="text-[11px] font-800 uppercase tracking-[0.08em]">{check.status}</div>
|
|
355
|
+
<div className="mt-1 text-[12px] font-700 text-text">{check.title}</div>
|
|
356
|
+
<div className="mt-0.5 text-[11px] leading-relaxed text-text-3/70">{check.summary}</div>
|
|
357
|
+
</button>
|
|
358
|
+
))
|
|
359
|
+
) : (
|
|
360
|
+
report.nextActions.slice(0, 5).map((action) => (
|
|
361
|
+
<button
|
|
362
|
+
key={action.id}
|
|
363
|
+
type="button"
|
|
364
|
+
onClick={() => onOpenHref(action.href)}
|
|
365
|
+
className={cn('rounded-[10px] border px-3 py-2 text-left transition-colors hover:bg-white/[0.08]', architectureStatusClass(action.severity))}
|
|
366
|
+
>
|
|
367
|
+
<div className="text-[11px] font-800 uppercase tracking-[0.08em]">{action.severity}</div>
|
|
368
|
+
<div className="mt-1 text-[12px] font-700 text-text">{action.title}</div>
|
|
369
|
+
<div className="mt-0.5 text-[11px] leading-relaxed text-text-3/70">{action.summary}</div>
|
|
370
|
+
</button>
|
|
371
|
+
))
|
|
372
|
+
)}
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
)}
|
|
378
|
+
</section>
|
|
379
|
+
)
|
|
380
|
+
}
|
|
381
|
+
|
|
257
382
|
function EvalEnvironmentPanel({ plan, loading, onRefresh }: {
|
|
258
383
|
plan: EvalEnvironmentPlan | null
|
|
259
384
|
loading: boolean
|
|
@@ -471,6 +596,8 @@ export function QualityWorkspace() {
|
|
|
471
596
|
const [evalBaselineBusy, setEvalBaselineBusy] = useState(false)
|
|
472
597
|
const [releaseReadiness, setReleaseReadiness] = useState<ReleaseReadinessReport | null>(null)
|
|
473
598
|
const [releaseReadinessLoading, setReleaseReadinessLoading] = useState(false)
|
|
599
|
+
const [architectureHealth, setArchitectureHealth] = useState<ArchitectureHealthReport | null>(null)
|
|
600
|
+
const [architectureHealthLoading, setArchitectureHealthLoading] = useState(false)
|
|
474
601
|
const [approvalBusy, setApprovalBusy] = useState<string | null>(null)
|
|
475
602
|
|
|
476
603
|
useEffect(() => {
|
|
@@ -578,10 +705,27 @@ export function QualityWorkspace() {
|
|
|
578
705
|
}
|
|
579
706
|
}, [evalGateScope, selectedAgentId, selectedScenarioId, selectedSuite])
|
|
580
707
|
|
|
708
|
+
const loadArchitectureHealth = useCallback(async () => {
|
|
709
|
+
setArchitectureHealthLoading(true)
|
|
710
|
+
try {
|
|
711
|
+
const report = await api<ArchitectureHealthReport>('GET', '/quality/architecture-health')
|
|
712
|
+
setArchitectureHealth(report)
|
|
713
|
+
} catch (err) {
|
|
714
|
+
setArchitectureHealth(null)
|
|
715
|
+
toast.error(err instanceof Error ? err.message : 'Unable to check architecture health')
|
|
716
|
+
} finally {
|
|
717
|
+
setArchitectureHealthLoading(false)
|
|
718
|
+
}
|
|
719
|
+
}, [])
|
|
720
|
+
|
|
581
721
|
useEffect(() => {
|
|
582
722
|
void loadQualityData()
|
|
583
723
|
}, [loadQualityData])
|
|
584
724
|
|
|
725
|
+
useEffect(() => {
|
|
726
|
+
void loadArchitectureHealth()
|
|
727
|
+
}, [loadArchitectureHealth])
|
|
728
|
+
|
|
585
729
|
useWs('runs', () => { void loadQualityData({ silent: true }) }, 5000)
|
|
586
730
|
|
|
587
731
|
useEffect(() => {
|
|
@@ -746,7 +890,11 @@ export function QualityWorkspace() {
|
|
|
746
890
|
{refreshing && <span className="text-[11px] text-text-3/60">Refreshing...</span>}
|
|
747
891
|
<button
|
|
748
892
|
type="button"
|
|
749
|
-
onClick={() =>
|
|
893
|
+
onClick={() => {
|
|
894
|
+
void loadQualityData({ silent: true })
|
|
895
|
+
void loadArchitectureHealth()
|
|
896
|
+
void loadReleaseReadiness()
|
|
897
|
+
}}
|
|
750
898
|
className="inline-flex items-center gap-2 rounded-[10px] border border-white/[0.08] bg-white/[0.04] px-3 py-2 text-[12px] font-700 text-text-2 transition-colors hover:bg-white/[0.08]"
|
|
751
899
|
>
|
|
752
900
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
@@ -790,6 +938,12 @@ export function QualityWorkspace() {
|
|
|
790
938
|
onRefresh={() => void loadReleaseReadiness()}
|
|
791
939
|
onOpenHref={(href) => router.push(href)}
|
|
792
940
|
/>
|
|
941
|
+
<ArchitectureHealthPanel
|
|
942
|
+
report={architectureHealth}
|
|
943
|
+
loading={architectureHealthLoading}
|
|
944
|
+
onRefresh={() => void loadArchitectureHealth()}
|
|
945
|
+
onOpenHref={(href) => router.push(href)}
|
|
946
|
+
/>
|
|
793
947
|
|
|
794
948
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
|
795
949
|
<StatTile
|
|
@@ -92,7 +92,7 @@ function CommandPaletteInner({ setOpen }: { setOpen: (v: boolean) => void }) {
|
|
|
92
92
|
{ id: 'projects', label: 'Projects', description: 'Scoped workspaces for agents and tasks', keywords: ['workspace', 'scope'] },
|
|
93
93
|
{ id: 'chatrooms', label: 'Chatrooms', description: 'Shared multi-agent conversations', keywords: ['group', 'room', 'mentions'] },
|
|
94
94
|
{ id: 'schedules', label: 'Schedules', description: 'Recurring and timed automations', keywords: ['cron', 'automation', 'interval'] },
|
|
95
|
-
{ id: 'connectors', label: 'Connectors', description: 'Bridges to Slack, Discord, Telegram, and more', keywords: ['discord', 'slack', 'telegram', 'whatsapp'] },
|
|
95
|
+
{ id: 'connectors', label: 'Connectors', description: 'Bridges to Slack, Discord, Telegram, file queues, and more', keywords: ['discord', 'slack', 'telegram', 'whatsapp', 'file queue'] },
|
|
96
96
|
{ id: 'memory', label: 'Memory', description: 'Stored agent memory and retrieval', keywords: ['knowledge', 'vector', 'retrieval'] },
|
|
97
97
|
{ id: 'knowledge', label: 'Knowledge', description: 'Shared knowledge base', keywords: ['docs', 'entries', 'facts'] },
|
|
98
98
|
{ id: 'providers', label: 'Providers', description: 'Model providers and endpoints', keywords: ['openai', 'anthropic', 'ollama', 'endpoint'] },
|
|
@@ -8,6 +8,10 @@ import {
|
|
|
8
8
|
|
|
9
9
|
describe('connector platform metadata', () => {
|
|
10
10
|
it('resolves legacy connector platforms used by stored runtime data', () => {
|
|
11
|
+
assert.deepEqual(resolveConnectorPlatformMeta('filequeue'), {
|
|
12
|
+
label: 'File Queue',
|
|
13
|
+
color: '#22C55E',
|
|
14
|
+
})
|
|
11
15
|
assert.deepEqual(resolveConnectorPlatformMeta('webchat'), {
|
|
12
16
|
label: 'Web Chat',
|
|
13
17
|
color: '#0EA5E9',
|
|
@@ -26,6 +26,7 @@ export const CONNECTOR_PLATFORM_META: Record<ConnectorPlatform, { label: string;
|
|
|
26
26
|
email: { label: 'Email', color: '#EA4335' },
|
|
27
27
|
webchat: { label: 'Web Chat', color: '#0EA5E9' },
|
|
28
28
|
mockmail: { label: 'MockMail', color: '#7C3AED' },
|
|
29
|
+
filequeue: { label: 'File Queue', color: '#22C55E' },
|
|
29
30
|
swarmdock: { label: 'SwarmDock', color: '#F59E0B' },
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -4,7 +4,7 @@ export type ConnectorReadinessState = 'needs_setup' | 'attention' | 'healthy'
|
|
|
4
4
|
export type ConnectorReadinessCheckStatus = 'ready' | 'warning' | 'error'
|
|
5
5
|
|
|
6
6
|
export interface ConnectorReadinessCheck {
|
|
7
|
-
id: 'credentials' | 'route' | 'pairing' | 'connection' | 'gateway'
|
|
7
|
+
id: 'credentials' | 'route' | 'pairing' | 'connection' | 'gateway' | 'queue'
|
|
8
8
|
label: string
|
|
9
9
|
status: ConnectorReadinessCheckStatus
|
|
10
10
|
detail: string
|
|
@@ -26,6 +26,7 @@ export function hasConnectorCredentials(connector: Connector): boolean {
|
|
|
26
26
|
|| connector.platform === 'openclaw'
|
|
27
27
|
|| connector.platform === 'signal'
|
|
28
28
|
|| connector.platform === 'email'
|
|
29
|
+
|| connector.platform === 'filequeue'
|
|
29
30
|
|| connector.platform === 'swarmdock'
|
|
30
31
|
|| (connector.platform === 'bluebubbles' && (!!connector.credentialId || !!connector.config?.password))
|
|
31
32
|
|| !!connector.credentialId
|
|
@@ -53,12 +54,21 @@ export function getConnectorReadiness(connector: Connector): ConnectorReadiness
|
|
|
53
54
|
const credentialsReady = hasConnectorCredentials(connector)
|
|
54
55
|
const routeReady = hasRoute(connector)
|
|
55
56
|
const checks: ConnectorReadinessCheck[] = [
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
connector.platform === 'filequeue'
|
|
58
|
+
? {
|
|
59
|
+
id: 'queue',
|
|
60
|
+
label: 'Queue folders',
|
|
61
|
+
status: 'ready',
|
|
62
|
+
detail: connector.config?.rootDir
|
|
63
|
+
? `Watching ${connector.config.rootDir}`
|
|
64
|
+
: 'Using the managed local queue folder.',
|
|
65
|
+
}
|
|
66
|
+
: {
|
|
67
|
+
id: 'credentials',
|
|
68
|
+
label: 'Credentials',
|
|
69
|
+
status: credentialsReady ? 'ready' : 'error',
|
|
70
|
+
detail: credentialsReady ? 'Credential path is configured.' : 'Add the token, password, or pairing credential.',
|
|
71
|
+
},
|
|
62
72
|
{
|
|
63
73
|
id: 'route',
|
|
64
74
|
label: 'Route target',
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, it } from 'node:test'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
buildArchitectureHealthReport,
|
|
6
|
+
DEFAULT_ARCHITECTURE_HEALTH_INVENTORY,
|
|
7
|
+
} from './architecture-health'
|
|
8
|
+
|
|
9
|
+
describe('architecture health report', () => {
|
|
10
|
+
it('summarizes the default runtime architecture inventory', () => {
|
|
11
|
+
const report = buildArchitectureHealthReport({
|
|
12
|
+
generatedAt: 100_000,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
assert.equal(report.generatedAt, 100_000)
|
|
16
|
+
assert.equal(report.status, 'healthy')
|
|
17
|
+
assert.equal(report.score, 100)
|
|
18
|
+
assert.equal(report.domainCount, DEFAULT_ARCHITECTURE_HEALTH_INVENTORY.length)
|
|
19
|
+
assert.ok(report.surfaceCount >= 10)
|
|
20
|
+
assert.ok(report.guardrailCount >= 8)
|
|
21
|
+
assert.ok(report.checks.some((check) => check.code === 'dispatch_guardrail_coverage'))
|
|
22
|
+
assert.ok(report.checks.some((check) => check.code === 'memory_authority'))
|
|
23
|
+
assert.ok(report.checks.some((check) => check.code === 'startup_surface_inventory'))
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('warns when a domain has an unguarded surface', () => {
|
|
27
|
+
const report = buildArchitectureHealthReport({
|
|
28
|
+
generatedAt: 100_000,
|
|
29
|
+
inventory: [{
|
|
30
|
+
id: 'dispatch',
|
|
31
|
+
title: 'Dispatch',
|
|
32
|
+
summary: 'Test dispatch surface',
|
|
33
|
+
owner: 'runtime',
|
|
34
|
+
surfaces: [{
|
|
35
|
+
id: 'direct',
|
|
36
|
+
title: 'Direct run',
|
|
37
|
+
kind: 'dispatch',
|
|
38
|
+
path: 'src/lib/server/test.ts',
|
|
39
|
+
description: 'A dispatch path without a guardrail.',
|
|
40
|
+
guardrails: [],
|
|
41
|
+
evidence: ['No policy attached'],
|
|
42
|
+
}],
|
|
43
|
+
testPaths: ['src/lib/server/test.test.ts'],
|
|
44
|
+
}],
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
assert.equal(report.status, 'watch')
|
|
48
|
+
assert.ok(report.score < 100)
|
|
49
|
+
assert.equal(report.warningCount, 1)
|
|
50
|
+
assert.ok(report.checks.some((check) => check.code === 'dispatch_unguarded_surface'))
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('marks missing test coverage as an architecture risk', () => {
|
|
54
|
+
const report = buildArchitectureHealthReport({
|
|
55
|
+
generatedAt: 100_000,
|
|
56
|
+
inventory: [{
|
|
57
|
+
id: 'startup',
|
|
58
|
+
title: 'Startup',
|
|
59
|
+
summary: 'Startup entry points',
|
|
60
|
+
owner: 'runtime',
|
|
61
|
+
surfaces: [{
|
|
62
|
+
id: 'cli',
|
|
63
|
+
title: 'CLI',
|
|
64
|
+
kind: 'startup',
|
|
65
|
+
path: 'src/cli/index.js',
|
|
66
|
+
description: 'CLI startup surface.',
|
|
67
|
+
guardrails: ['route coverage'],
|
|
68
|
+
evidence: ['CLI starts the server'],
|
|
69
|
+
}],
|
|
70
|
+
testPaths: [],
|
|
71
|
+
}],
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
assert.equal(report.status, 'risk')
|
|
75
|
+
assert.ok(report.score <= 70)
|
|
76
|
+
assert.equal(report.riskCount, 1)
|
|
77
|
+
assert.ok(report.checks.some((check) => check.code === 'startup_missing_tests'))
|
|
78
|
+
})
|
|
79
|
+
})
|