@polderlabs/bizar 6.2.5 → 7.0.0
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/.claude/CLAUDE.md +15 -0
- package/.claude/agents/_shared/AGENT_BASELINE.md +168 -0
- package/.claude/agents/_shared/CLAUDE_TOOLS.md +412 -0
- package/.claude/agents/_shared/SKILLS.md +109 -0
- package/.claude/agents/agent-browser.md +80 -0
- package/.claude/agents/baldr.md +49 -0
- package/.claude/agents/forseti.md +51 -0
- package/.claude/agents/frigg.md +42 -0
- package/.claude/agents/heimdall.md +33 -0
- package/.claude/agents/hermod.md +53 -0
- package/.claude/agents/mimir.md +49 -0
- package/.claude/agents/odin.md +287 -0
- package/.claude/agents/quick.md +34 -0
- package/.claude/agents/semble-search.md +50 -0
- package/.claude/agents/thor.md +53 -0
- package/.claude/agents/tyr.md +56 -0
- package/.claude/agents/vidarr.md +54 -0
- package/.claude/agents/vor.md +53 -0
- package/.claude/commands/audit.md +25 -0
- package/.claude/commands/bizar.md +22 -0
- package/.claude/commands/explain.md +17 -0
- package/.claude/commands/init.md +29 -0
- package/.claude/commands/learn.md +44 -0
- package/.claude/commands/plan.md +35 -0
- package/.claude/commands/plow-through.md +50 -0
- package/.claude/commands/pr-review.md +49 -0
- package/.claude/commands/setup-provider.md +96 -0
- package/.claude/commands/tailscale-serve.md +100 -0
- package/.claude/commands/team.md +132 -0
- package/.claude/commands/test.md +62 -0
- package/.claude/commands/validate.md +68 -0
- package/.claude/commands/visual-plan.md +24 -0
- package/.claude/hooks/README.md +92 -0
- package/.claude/hooks/posttooluse-editwrite.mjs +91 -0
- package/.claude/hooks/pretooluse-bash.mjs +81 -0
- package/.claude/hooks/pretooluse-editwrite.mjs +139 -0
- package/.claude/hooks/sessionend-recall.mjs +74 -0
- package/.claude/hooks/sessionstart-prime.mjs +80 -0
- package/.claude/hooks/userpromptsubmit-tag.mjs +80 -0
- package/.claude/hooks/worker-suggest.mjs +110 -0
- package/.claude/settings.json +125 -0
- package/.claude/skills/9router/SKILL.md +80 -0
- package/.claude/skills/9router-chat/SKILL.md +73 -0
- package/.claude/skills/9router-embeddings/SKILL.md +69 -0
- package/.claude/skills/9router-image/SKILL.md +86 -0
- package/.claude/skills/9router-stt/SKILL.md +79 -0
- package/.claude/skills/9router-tts/SKILL.md +80 -0
- package/.claude/skills/9router-web-fetch/SKILL.md +99 -0
- package/.claude/skills/9router-web-search/SKILL.md +91 -0
- package/.claude/skills/bizar/README.md +9 -0
- package/.claude/skills/bizar/SKILL.md +450 -0
- package/.claude/skills/cpp-coding-standards/README.md +28 -0
- package/.claude/skills/cpp-coding-standards/SKILL.md +634 -0
- package/.claude/skills/cpp-coding-standards/references/concurrency.md +320 -0
- package/.claude/skills/cpp-coding-standards/references/error-handling.md +229 -0
- package/.claude/skills/cpp-coding-standards/references/memory-safety.md +216 -0
- package/.claude/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
- package/.claude/skills/cpp-coding-standards/references/review-checklist.md +96 -0
- package/.claude/skills/cpp-testing/README.md +28 -0
- package/.claude/skills/cpp-testing/SKILL.md +304 -0
- package/.claude/skills/cpp-testing/references/coverage.md +370 -0
- package/.claude/skills/cpp-testing/references/framework-compare.md +175 -0
- package/.claude/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
- package/.claude/skills/cpp-testing/references/mocking.md +364 -0
- package/.claude/skills/cpp-testing/references/tdd-workflow.md +308 -0
- package/.claude/skills/cubesandbox/SKILL.md +148 -0
- package/.claude/skills/embedded-esp-idf/README.md +41 -0
- package/.claude/skills/embedded-esp-idf/SKILL.md +439 -0
- package/.claude/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
- package/.claude/skills/embedded-esp-idf/references/host-tests.md +164 -0
- package/.claude/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
- package/.claude/skills/embedded-esp-idf/references/kconfig.md +159 -0
- package/.claude/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
- package/.claude/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
- package/.claude/skills/embedded-esp-idf/references/nvs.md +121 -0
- package/.claude/skills/embedded-esp-idf/references/packed-structs.md +192 -0
- package/.claude/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
- package/.claude/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
- package/.claude/skills/glyph/SKILL.md +163 -0
- package/.claude/skills/harness-engineering/SKILL.md +143 -0
- package/.claude/skills/lightrag/SKILL.md +81 -0
- package/.claude/skills/memory-protocol/SKILL.md +105 -0
- package/.claude/skills/obsidian/SKILL.md +306 -0
- package/.claude/skills/read-the-damn-docs/SKILL.md +113 -0
- package/.claude/skills/self-improvement/SKILL.md +64 -0
- package/README.md +87 -59
- package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js → EnvVarsSection-CW6fWKds.js} +3 -3
- package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js.map → EnvVarsSection-CW6fWKds.js.map} +1 -1
- package/bizar-dash/dist/assets/MobileChat-D7zRhU9K.js +1 -0
- package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js.map → MobileChat-D7zRhU9K.js.map} +1 -1
- package/bizar-dash/dist/assets/MobileSettings-K-sNsn_r.js +1 -0
- package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js.map → MobileSettings-K-sNsn_r.js.map} +1 -1
- package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js → Toast-WTxc3m2g.js} +1 -1
- package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js.map → Toast-WTxc3m2g.js.map} +1 -1
- package/bizar-dash/dist/assets/flow-BpU0-gK2.js +1 -0
- package/bizar-dash/dist/assets/flow-BpU0-gK2.js.map +1 -0
- package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js +1 -0
- package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js.map +1 -0
- package/bizar-dash/dist/assets/{icons-Do5N2jmX.js → icons-B0AKnYI3.js} +98 -103
- package/bizar-dash/dist/assets/icons-B0AKnYI3.js.map +1 -0
- package/bizar-dash/dist/assets/main-C-Md3DaY.css +1 -0
- package/bizar-dash/dist/assets/main-CO8fIxQK.js +18 -0
- package/bizar-dash/dist/assets/main-CO8fIxQK.js.map +1 -0
- package/bizar-dash/dist/assets/markdown-w2bDDkAH.js +1 -0
- package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js.map → markdown-w2bDDkAH.js.map} +1 -1
- package/bizar-dash/dist/assets/mobile-DXYCA0hC.js +1 -0
- package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js.map → mobile-DXYCA0hC.js.map} +1 -1
- package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js +2 -0
- package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js.map +1 -0
- package/bizar-dash/dist/assets/react-vendor-BnuhLJ6X.css +1 -0
- package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js +46 -0
- package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js.map +1 -0
- package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js +2 -0
- package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js.map +1 -0
- package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js +45 -0
- package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js.map +1 -0
- package/bizar-dash/dist/index.html +12 -10
- package/bizar-dash/dist/mobile.html +7 -4
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/bizar-dash/src/server/api.mjs +15 -4
- package/bizar-dash/src/server/background-store.mjs +15 -16
- package/bizar-dash/src/server/bg-poller.mjs +25 -35
- package/bizar-dash/src/server/bg-retry.mjs +66 -194
- package/bizar-dash/src/server/claude-bg-spawner.mjs +390 -0
- package/bizar-dash/src/server/claude-info.mjs +412 -0
- package/bizar-dash/src/server/{cline-runner.mjs → claude-runner.mjs} +53 -48
- package/bizar-dash/src/server/claude-sdk.mjs +191 -0
- package/bizar-dash/src/server/memory-consolidator.mjs +218 -0
- package/bizar-dash/src/server/memory-distillation.mjs +296 -0
- package/bizar-dash/src/server/providers-store.mjs +22 -49
- package/bizar-dash/src/server/routes/background.mjs +24 -20
- package/bizar-dash/src/server/routes/chat.mjs +223 -326
- package/bizar-dash/src/server/routes/claude-session-detail.mjs +329 -0
- package/bizar-dash/src/server/routes/claude-sessions.mjs +259 -0
- package/bizar-dash/src/server/routes/distill.mjs +129 -0
- package/bizar-dash/src/server/routes/goal-planner.mjs +236 -0
- package/bizar-dash/src/server/routes/tasks.mjs +20 -28
- package/bizar-dash/src/server/task-delegator.mjs +76 -77
- package/bizar-dash/src/web/App.tsx +4 -0
- package/bizar-dash/src/web/components/Tag.tsx +18 -5
- package/bizar-dash/src/web/components/Topbar.tsx +3 -0
- package/bizar-dash/src/web/components/agents/CommunicationLog.tsx +126 -0
- package/bizar-dash/src/web/components/agents/DependencyGraph.tsx +163 -0
- package/bizar-dash/src/web/components/agents/QualityGates.tsx +123 -0
- package/bizar-dash/src/web/components/agents/RealTimeEventLog.tsx +127 -0
- package/bizar-dash/src/web/components/agents/RoutingDecisions.test.tsx +148 -0
- package/bizar-dash/src/web/components/agents/RoutingDecisions.tsx +160 -0
- package/bizar-dash/src/web/components/chat/useChat.ts +23 -27
- package/bizar-dash/src/web/components/goals/GoalInput.tsx +86 -0
- package/bizar-dash/src/web/components/goals/PlanVisualization.tsx +111 -0
- package/bizar-dash/src/web/lib/goapPlanner.ts +614 -0
- package/bizar-dash/src/web/lib/types.ts +25 -0
- package/bizar-dash/src/web/styles/agents-redesign.css +311 -0
- package/bizar-dash/src/web/styles/main.css +251 -0
- package/bizar-dash/src/web/styles/tasks-redesign.css +231 -0
- package/bizar-dash/src/web/ui/controls/Button.tsx +77 -0
- package/bizar-dash/src/web/ui/controls/Checkbox.tsx +95 -0
- package/bizar-dash/src/web/ui/controls/IconButton.tsx +66 -0
- package/bizar-dash/src/web/ui/controls/Kbd.tsx +24 -0
- package/bizar-dash/src/web/ui/controls/NumberInput.tsx +77 -0
- package/bizar-dash/src/web/ui/controls/RadioGroup.tsx +109 -0
- package/bizar-dash/src/web/ui/controls/SearchInput.tsx +91 -0
- package/bizar-dash/src/web/ui/controls/Select.tsx +92 -0
- package/bizar-dash/src/web/ui/controls/Slider.tsx +70 -0
- package/bizar-dash/src/web/ui/controls/TextInput.tsx +67 -0
- package/bizar-dash/src/web/ui/controls/Toggle.tsx +79 -0
- package/bizar-dash/src/web/ui/controls/controls.css +454 -0
- package/bizar-dash/src/web/ui/controls/index.ts +33 -0
- package/bizar-dash/src/web/ui/data/BarChart.tsx +60 -0
- package/bizar-dash/src/web/ui/data/DataTable.tsx +216 -0
- package/bizar-dash/src/web/ui/data/EmptyState.tsx +60 -0
- package/bizar-dash/src/web/ui/data/ErrorState.tsx +56 -0
- package/bizar-dash/src/web/ui/data/KeyValueList.tsx +106 -0
- package/bizar-dash/src/web/ui/data/LoadingState.tsx +65 -0
- package/bizar-dash/src/web/ui/data/Sparkline.tsx +87 -0
- package/bizar-dash/src/web/ui/data/StatTile.tsx +87 -0
- package/bizar-dash/src/web/ui/data/data.css +535 -0
- package/bizar-dash/src/web/ui/data/index.ts +31 -0
- package/bizar-dash/src/web/ui/feedback/Badge.tsx +49 -0
- package/bizar-dash/src/web/ui/feedback/Dialog.tsx +209 -0
- package/bizar-dash/src/web/ui/feedback/ProgressBar.tsx +69 -0
- package/bizar-dash/src/web/ui/feedback/StatusDot.tsx +50 -0
- package/bizar-dash/src/web/ui/feedback/Toast.tsx +200 -0
- package/bizar-dash/src/web/ui/feedback/Tooltip.tsx +126 -0
- package/bizar-dash/src/web/ui/feedback/feedback.css +314 -0
- package/bizar-dash/src/web/ui/feedback/index.ts +43 -0
- package/bizar-dash/src/web/ui/index.ts +56 -0
- package/bizar-dash/src/web/ui/layout/AppShell.tsx +51 -0
- package/bizar-dash/src/web/ui/layout/Breadcrumbs.tsx +97 -0
- package/bizar-dash/src/web/ui/layout/Panel.tsx +73 -0
- package/bizar-dash/src/web/ui/layout/PanelHeader.tsx +39 -0
- package/bizar-dash/src/web/ui/layout/Sidebar.tsx +131 -0
- package/bizar-dash/src/web/ui/layout/Tabs.tsx +133 -0
- package/bizar-dash/src/web/ui/layout/Topbar.tsx +42 -0
- package/bizar-dash/src/web/ui/layout/index.ts +33 -0
- package/bizar-dash/src/web/ui/layout/layout.css +413 -0
- package/bizar-dash/src/web/ui/navigation/CommandPalette.tsx +199 -0
- package/bizar-dash/src/web/ui/navigation/NavGroup.tsx +95 -0
- package/bizar-dash/src/web/ui/navigation/NavLink.tsx +93 -0
- package/bizar-dash/src/web/ui/navigation/index.ts +17 -0
- package/bizar-dash/src/web/ui/navigation/navigation.css +343 -0
- package/bizar-dash/src/web/ui/primitives/Box.tsx +91 -0
- package/bizar-dash/src/web/ui/primitives/Grid.tsx +49 -0
- package/bizar-dash/src/web/ui/primitives/Inline.tsx +55 -0
- package/bizar-dash/src/web/ui/primitives/Separator.tsx +46 -0
- package/bizar-dash/src/web/ui/primitives/Stack.tsx +54 -0
- package/bizar-dash/src/web/ui/primitives/VisuallyHidden.tsx +26 -0
- package/bizar-dash/src/web/ui/primitives/index.ts +23 -0
- package/bizar-dash/src/web/ui/primitives/primitives.css +183 -0
- package/bizar-dash/src/web/ui/styles/globals.css +86 -0
- package/bizar-dash/src/web/ui/styles/reset.css +125 -0
- package/bizar-dash/src/web/ui/styles/tokens.css +195 -0
- package/bizar-dash/src/web/ui/theme/ThemeProvider.tsx +142 -0
- package/bizar-dash/src/web/ui/theme/tokens.ts +131 -0
- package/bizar-dash/src/web/ui/theme/useTheme.ts +22 -0
- package/bizar-dash/src/web/ui/utils/cx.ts +42 -0
- package/bizar-dash/src/web/views/Agents.tsx +506 -266
- package/bizar-dash/src/web/views/Chat.tsx +8 -8
- package/bizar-dash/src/web/views/GoalPlanner.tsx +158 -0
- package/bizar-dash/src/web/views/Overview.tsx +996 -448
- package/bizar-dash/src/web/views/Tasks.tsx +277 -273
- package/bizar-dash/tests/communication-log.test.tsx +67 -0
- package/bizar-dash/tests/dep-graph.test.tsx +40 -0
- package/bizar-dash/tests/event-log.test.tsx +61 -0
- package/bizar-dash/tests/goal-input.test.tsx +72 -0
- package/bizar-dash/tests/goap-planner.test.ts +68 -0
- package/bizar-dash/tests/quality-gates.test.tsx +59 -0
- package/bizar-dash/tests/setup.ts +31 -1
- package/bizar-dash/tests/ui/controls/Button.test.tsx +50 -0
- package/bizar-dash/tests/ui/controls/Checkbox.test.tsx +44 -0
- package/bizar-dash/tests/ui/controls/IconButton.test.tsx +37 -0
- package/bizar-dash/tests/ui/controls/Kbd.test.tsx +39 -0
- package/bizar-dash/tests/ui/controls/NumberInput.test.tsx +56 -0
- package/bizar-dash/tests/ui/controls/RadioGroup.test.tsx +58 -0
- package/bizar-dash/tests/ui/controls/SearchInput.test.tsx +52 -0
- package/bizar-dash/tests/ui/controls/Select.test.tsx +66 -0
- package/bizar-dash/tests/ui/controls/Slider.test.tsx +41 -0
- package/bizar-dash/tests/ui/controls/TextInput.test.tsx +44 -0
- package/bizar-dash/tests/ui/controls/Toggle.test.tsx +39 -0
- package/bizar-dash/tests/ui/data/BarChart.test.tsx +55 -0
- package/bizar-dash/tests/ui/data/DataTable.test.tsx +112 -0
- package/bizar-dash/tests/ui/data/EmptyState.test.tsx +57 -0
- package/bizar-dash/tests/ui/data/ErrorState.test.tsx +47 -0
- package/bizar-dash/tests/ui/data/KeyValueList.test.tsx +59 -0
- package/bizar-dash/tests/ui/data/LoadingState.test.tsx +37 -0
- package/bizar-dash/tests/ui/data/Sparkline.test.tsx +56 -0
- package/bizar-dash/tests/ui/data/StatTile.test.tsx +69 -0
- package/bizar-dash/tests/ui/feedback/Badge.test.tsx +41 -0
- package/bizar-dash/tests/ui/feedback/Dialog.test.tsx +99 -0
- package/bizar-dash/tests/ui/feedback/ProgressBar.test.tsx +66 -0
- package/bizar-dash/tests/ui/feedback/StatusDot.test.tsx +41 -0
- package/bizar-dash/tests/ui/feedback/Toast.test.tsx +124 -0
- package/bizar-dash/tests/ui/feedback/Tooltip.test.tsx +102 -0
- package/bizar-dash/tests/ui/layout/AppShell.test.tsx +50 -0
- package/bizar-dash/tests/ui/layout/Breadcrumbs.test.tsx +85 -0
- package/bizar-dash/tests/ui/layout/Panel.test.tsx +52 -0
- package/bizar-dash/tests/ui/layout/PanelHeader.test.tsx +32 -0
- package/bizar-dash/tests/ui/layout/Sidebar.test.tsx +95 -0
- package/bizar-dash/tests/ui/layout/Tabs.test.tsx +87 -0
- package/bizar-dash/tests/ui/layout/Topbar.test.tsx +41 -0
- package/bizar-dash/tests/ui/navigation/CommandPalette.test.tsx +105 -0
- package/bizar-dash/tests/ui/navigation/NavGroup.test.tsx +55 -0
- package/bizar-dash/tests/ui/navigation/NavLink.test.tsx +61 -0
- package/bizar-dash/tests/ui/primitives/Box.test.tsx +49 -0
- package/bizar-dash/tests/ui/primitives/Grid.test.tsx +37 -0
- package/bizar-dash/tests/ui/primitives/Inline.test.tsx +33 -0
- package/bizar-dash/tests/ui/primitives/Separator.test.tsx +34 -0
- package/bizar-dash/tests/ui/primitives/Stack.test.tsx +45 -0
- package/bizar-dash/tests/ui/primitives/VisuallyHidden.test.tsx +27 -0
- package/bizar-dash/tests/views/Agents.test.tsx +359 -0
- package/bizar-dash/tests/views/Overview.test.tsx +413 -0
- package/bizar-dash/tests/views/Tasks.test.tsx +250 -0
- package/bizar-dash/vitest.config.ts +3 -1
- package/cli/__tests__/cost-gate.test.mjs +301 -0
- package/cli/__tests__/feature-list-bridge.test.mjs +371 -0
- package/cli/bin.mjs +55 -18
- package/cli/commands/claim.mjs +282 -0
- package/cli/commands/claude-cmd.mjs +348 -0
- package/cli/commands/cost.mjs +279 -0
- package/cli/commands/install.mjs +12 -11
- package/cli/commands/validate.mjs +252 -325
- package/cli/cost-gate.mjs +501 -0
- package/cli/dev-link.test.mjs +11 -11
- package/cli/doctor.mjs +131 -213
- package/cli/feature-list-bridge.mjs +400 -0
- package/cli/install.mjs +33 -37
- package/cli/install.test.mjs +9 -9
- package/cli/memory-constants.mjs +17 -0
- package/cli/provision-claude.mjs +893 -0
- package/cli/provision.mjs +224 -227
- package/cli/utils.mjs +72 -58
- package/cli/worker-dispatcher.mjs +259 -0
- package/cli/worker-dispatcher.test.mjs +204 -0
- package/config/AGENTS.md +28 -28
- package/config/trigger-patterns.json +186 -0
- package/install.sh +66 -44
- package/package.json +19 -11
- package/packages/sdk/ARCHITECTURE.md +40 -36
- package/packages/sdk/package.json +15 -5
- package/packages/sdk/src/agent-registry.ts +304 -0
- package/packages/sdk/src/consensus/byzantine.ts +587 -0
- package/packages/sdk/src/consensus/index.ts +147 -0
- package/packages/sdk/src/consensus/queen.ts +173 -0
- package/packages/sdk/src/consensus/types.ts +199 -0
- package/{plugins/bizar → packages/sdk}/src/dangerous-patterns.ts +6 -28
- package/packages/sdk/src/federation/audit.ts +156 -0
- package/packages/sdk/src/federation/budget.ts +269 -0
- package/packages/sdk/src/federation/envelope.ts +144 -0
- package/packages/sdk/src/federation/hmac.ts +160 -0
- package/packages/sdk/src/federation/index.ts +443 -0
- package/packages/sdk/src/federation/pii.ts +241 -0
- package/packages/sdk/src/federation/policy.ts +112 -0
- package/packages/sdk/src/federation/trust.ts +146 -0
- package/packages/sdk/src/fingerprint.ts +0 -0
- package/packages/sdk/src/index.ts +101 -69
- package/packages/sdk/src/mcp/bin.ts +51 -0
- package/packages/sdk/src/mcp/server.ts +848 -0
- package/{plugins/bizar/src/memory-vault.ts → packages/sdk/src/memory/index.ts} +18 -21
- package/packages/sdk/src/router/codemod-intent.ts +125 -0
- package/packages/sdk/src/router/index.ts +247 -0
- package/packages/sdk/src/router/memory-distillation-shim.mjs +18 -0
- package/packages/sdk/src/router/memory-distillation.ts +249 -0
- package/packages/sdk/src/router/model-router.ts +235 -0
- package/packages/sdk/src/router/q-learning-router.ts +304 -0
- package/packages/sdk/src/swarm-topology.ts +279 -0
- package/packages/sdk/tests/agent-registry.test.ts +285 -0
- package/packages/sdk/tests/consensus.test.ts +376 -0
- package/packages/sdk/tests/federation/audit.test.ts +164 -0
- package/packages/sdk/tests/federation/budget.test.ts +189 -0
- package/packages/sdk/tests/federation/envelope.test.ts +120 -0
- package/packages/sdk/tests/federation/hmac.test.ts +194 -0
- package/packages/sdk/tests/federation/orchestrator.test.ts +176 -0
- package/packages/sdk/tests/federation/pii.test.ts +106 -0
- package/packages/sdk/tests/federation/policy.test.ts +140 -0
- package/packages/sdk/tests/federation/trust.test.ts +124 -0
- package/packages/sdk/tests/mcp-tools.test.ts +204 -0
- package/packages/sdk/tests/memory-distillation.test.mjs +186 -0
- package/packages/sdk/tests/model-router.test.mjs +108 -0
- package/packages/sdk/tests/q-learning-router.test.mjs +100 -0
- package/packages/sdk/tests/router-orchestrator.test.mjs +172 -0
- package/packages/sdk/tests/router.test.mjs +91 -0
- package/packages/sdk/tests/sdk.test.mjs +172 -0
- package/packages/sdk/tests/swarm-topology.test.ts +258 -0
- package/packages/sdk/vitest.config.ts +1 -1
- package/scripts/bh-full-e2e.mjs +167 -368
- package/scripts/check-deps.mjs +28 -46
- package/scripts/mirror-claude-md.sh +78 -0
- package/scripts/test-in-container.sh +31 -9
- package/templates/clean-state-checklist.md +3 -3
- package/templates/sprint-contract.md +2 -2
- package/bizar-dash/.bizar/activity.log +0 -3
- package/bizar-dash/.omx/logs/omx-2026-07-07.jsonl +0 -1
- package/bizar-dash/.omx/state/session.json +0 -10
- package/bizar-dash/bizar-design/EXPLANATION.md +0 -307
- package/bizar-dash/bizar-design/INSTRUCTIONS.md +0 -149
- package/bizar-dash/bizar-design/canvas.html +0 -281
- package/bizar-dash/bizar-design/canvas.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/components.css +0 -1665
- package/bizar-dash/bizar-design/components.html +0 -557
- package/bizar-dash/bizar-design/components.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/agents.html +0 -74
- package/bizar-dash/bizar-design/desktop/agents.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/memory.html +0 -75
- package/bizar-dash/bizar-design/desktop/memory.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/metrics.html +0 -63
- package/bizar-dash/bizar-design/desktop/metrics.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/overview.html +0 -95
- package/bizar-dash/bizar-design/desktop/overview.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/settings.html +0 -70
- package/bizar-dash/bizar-design/desktop/settings.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/icons.svg +0 -44
- package/bizar-dash/bizar-design/index.html +0 -192
- package/bizar-dash/bizar-design/index.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/agents.html +0 -29
- package/bizar-dash/bizar-design/mobile/agents.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/memory.html +0 -28
- package/bizar-dash/bizar-design/mobile/memory.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/metrics.html +0 -29
- package/bizar-dash/bizar-design/mobile/metrics.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/overview.html +0 -33
- package/bizar-dash/bizar-design/mobile/overview.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/settings.html +0 -28
- package/bizar-dash/bizar-design/mobile/settings.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/prototype.js +0 -114
- package/bizar-dash/bizar-design/tokens.css +0 -106
- package/bizar-dash/dist/assets/MobileChat-BnKN_Ks_.js +0 -1
- package/bizar-dash/dist/assets/MobileSettings-DjCPxC-Q.js +0 -1
- package/bizar-dash/dist/assets/icons-Do5N2jmX.js.map +0 -1
- package/bizar-dash/dist/assets/main-DYiZqMrn.js +0 -18
- package/bizar-dash/dist/assets/main-DYiZqMrn.js.map +0 -1
- package/bizar-dash/dist/assets/main-ietCEg_R.css +0 -1
- package/bizar-dash/dist/assets/markdown-C6mXtQxD.js +0 -1
- package/bizar-dash/dist/assets/mobile-CMHqtLV2.js +0 -1
- package/bizar-dash/dist/assets/mobile-layout-3jIhHX_p.js +0 -2
- package/bizar-dash/dist/assets/mobile-layout-3jIhHX_p.js.map +0 -1
- package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js +0 -40
- package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js.map +0 -1
- package/bizar-dash/dist/assets/useSlashCommands-DtITw8Xv.js +0 -2
- package/bizar-dash/dist/assets/useSlashCommands-DtITw8Xv.js.map +0 -1
- package/bizar-dash/dist/assets/vendor-CeHGtduv.js +0 -29
- package/bizar-dash/dist/assets/vendor-CeHGtduv.js.map +0 -1
- package/bizar-dash/node_modules/.package-lock.json +0 -6
- package/bizar-dash/package-lock.json +0 -6
- package/bizar-dash/src/server/cline-sdk.mjs +0 -132
- package/bizar-dash/src/server/routes/cline-session-detail.mjs +0 -559
- package/bizar-dash/src/server/routes/cline-sessions.mjs +0 -291
- package/cli/commands/cline-cmd.mjs +0 -289
- package/cli/commands/validate.test.mjs +0 -379
- package/cli/doctor.test.mjs +0 -350
- package/config/cline.json.template +0 -342
- package/packages/sdk/src/client.ts +0 -188
- package/packages/sdk/src/cline-events.ts +0 -134
- package/packages/sdk/src/cline-types.ts +0 -66
- package/packages/sdk/src/cline.ts +0 -339
- package/packages/sdk/src/errors.ts +0 -129
- package/packages/sdk/src/events.ts +0 -153
- package/packages/sdk/src/types.ts +0 -176
- package/packages/sdk/tests/client.test.ts +0 -217
- package/packages/sdk/tests/errors.test.ts +0 -108
- package/packages/sdk/tests/events.test.ts +0 -139
- package/packages/sdk/tests/fixtures/fetch-mock.ts +0 -152
- package/packages/sdk/tests/fixtures/sse-mock.ts +0 -30
- package/plugins/bizar/ARCHITECTURE.md +0 -142
- package/plugins/bizar/CONSTRAINTS.md +0 -67
- package/plugins/bizar/LICENSE +0 -21
- package/plugins/bizar/README.md +0 -448
- package/plugins/bizar/index.ts +0 -925
- package/plugins/bizar/package.json +0 -39
- package/plugins/bizar/scripts/check-forbidden-imports.sh +0 -33
- package/plugins/bizar/src/background-state.ts +0 -641
- package/plugins/bizar/src/background.ts +0 -1806
- package/plugins/bizar/src/cline-runner.ts +0 -203
- package/plugins/bizar/src/clineruntime.ts +0 -227
- package/plugins/bizar/src/commands-impl.ts +0 -151
- package/plugins/bizar/src/commands.ts +0 -1799
- package/plugins/bizar/src/compaction.d.mts +0 -48
- package/plugins/bizar/src/compaction.mjs +0 -192
- package/plugins/bizar/src/dashboard-client.ts +0 -233
- package/plugins/bizar/src/event-stream.ts +0 -606
- package/plugins/bizar/src/fingerprint.ts +0 -120
- package/plugins/bizar/src/handoff.ts +0 -79
- package/plugins/bizar/src/hooks/memory-flush-on-compact.ts +0 -123
- package/plugins/bizar/src/hooks/memory-inject.ts +0 -247
- package/plugins/bizar/src/hooks/memory-write-on-end.ts +0 -188
- package/plugins/bizar/src/hooks/skill-curator.ts +0 -180
- package/plugins/bizar/src/http-client.ts +0 -467
- package/plugins/bizar/src/key-rotation.ts +0 -218
- package/plugins/bizar/src/logger.ts +0 -144
- package/plugins/bizar/src/loop-engineering.ts +0 -241
- package/plugins/bizar/src/loop.ts +0 -176
- package/plugins/bizar/src/mistake-recovery.ts +0 -98
- package/plugins/bizar/src/odin.ts +0 -227
- package/plugins/bizar/src/options.ts +0 -470
- package/plugins/bizar/src/plan-fs.ts +0 -323
- package/plugins/bizar/src/reasoning-clean.ts +0 -454
- package/plugins/bizar/src/report.ts +0 -178
- package/plugins/bizar/src/research-prompt.ts +0 -35
- package/plugins/bizar/src/serve-info.ts +0 -228
- package/plugins/bizar/src/serve.ts +0 -496
- package/plugins/bizar/src/settings.ts +0 -349
- package/plugins/bizar/src/state.ts +0 -298
- package/plugins/bizar/src/tool-discipline.ts +0 -105
- package/plugins/bizar/src/tools/agent-browser.ts +0 -315
- package/plugins/bizar/src/tools/bg-collect.ts +0 -131
- package/plugins/bizar/src/tools/bg-get-comments.ts +0 -266
- package/plugins/bizar/src/tools/bg-kill.ts +0 -116
- package/plugins/bizar/src/tools/bg-pause.ts +0 -99
- package/plugins/bizar/src/tools/bg-report-progress.ts +0 -115
- package/plugins/bizar/src/tools/bg-resume.ts +0 -94
- package/plugins/bizar/src/tools/bg-send-message.ts +0 -223
- package/plugins/bizar/src/tools/bg-spawn.ts +0 -502
- package/plugins/bizar/src/tools/bg-status.ts +0 -130
- package/plugins/bizar/src/tools/graph-query.ts +0 -278
- package/plugins/bizar/src/tools/loop-engineering.ts +0 -193
- package/plugins/bizar/src/tools/memory-list.ts +0 -43
- package/plugins/bizar/src/tools/memory-read.ts +0 -69
- package/plugins/bizar/src/tools/memory-search.ts +0 -47
- package/plugins/bizar/src/tools/memory-write.ts +0 -54
- package/plugins/bizar/src/tools/open-kb.ts +0 -198
- package/plugins/bizar/src/tools/plan-action.ts +0 -785
- package/plugins/bizar/src/tools/read-glyph-feedback.ts +0 -191
- package/plugins/bizar/src/tools/sandbox.ts +0 -232
- package/plugins/bizar/src/tools/team-spawn.ts +0 -73
- package/plugins/bizar/src/tools/team-status.ts +0 -76
- package/plugins/bizar/src/tools/wait-for-feedback.ts +0 -415
- package/plugins/bizar/src/trajectory.ts +0 -104
- package/plugins/bizar/tests/README.md +0 -99
- package/plugins/bizar/tests/attach-handler-bug.test.ts +0 -169
- package/plugins/bizar/tests/background-state.test.ts +0 -277
- package/plugins/bizar/tests/background.test.ts +0 -402
- package/plugins/bizar/tests/block.test.ts +0 -195
- package/plugins/bizar/tests/canonical-key-order.test.ts +0 -75
- package/plugins/bizar/tests/clineruntime-config.test.ts +0 -283
- package/plugins/bizar/tests/commands-impl.test.ts +0 -316
- package/plugins/bizar/tests/commands.test.ts +0 -584
- package/plugins/bizar/tests/compaction.test.ts +0 -264
- package/plugins/bizar/tests/config.test.ts +0 -128
- package/plugins/bizar/tests/dashboard-client.test.ts +0 -159
- package/plugins/bizar/tests/dispose.test.ts +0 -336
- package/plugins/bizar/tests/event-stream.test.ts +0 -409
- package/plugins/bizar/tests/event.test.ts +0 -262
- package/plugins/bizar/tests/fingerprint.test.ts +0 -190
- package/plugins/bizar/tests/http-client.test.ts +0 -404
- package/plugins/bizar/tests/init-helpers.test.ts +0 -203
- package/plugins/bizar/tests/integration/slash-command.test.ts +0 -349
- package/plugins/bizar/tests/integration/tool-routing.test.ts +0 -98
- package/plugins/bizar/tests/key-rotation.test.ts +0 -396
- package/plugins/bizar/tests/loop-engineering.test.ts +0 -168
- package/plugins/bizar/tests/loop.test.ts +0 -397
- package/plugins/bizar/tests/memory-write-on-end.test.ts +0 -92
- package/plugins/bizar/tests/mistake-recovery.test.ts +0 -116
- package/plugins/bizar/tests/odin.test.ts +0 -125
- package/plugins/bizar/tests/options.test.ts +0 -329
- package/plugins/bizar/tests/reasoning-clean.test.ts +0 -422
- package/plugins/bizar/tests/safety.test.ts +0 -281
- package/plugins/bizar/tests/serve.test.ts +0 -339
- package/plugins/bizar/tests/settings.test.ts +0 -351
- package/plugins/bizar/tests/stall-think.test.ts +0 -750
- package/plugins/bizar/tests/state.test.ts +0 -276
- package/plugins/bizar/tests/tool-discipline.test.ts +0 -77
- package/plugins/bizar/tests/tools/agent-browser.test.ts +0 -98
- package/plugins/bizar/tests/tools/bg-collect.test.ts +0 -337
- package/plugins/bizar/tests/tools/bg-get-comments.test.ts +0 -485
- package/plugins/bizar/tests/tools/bg-kill.test.ts +0 -235
- package/plugins/bizar/tests/tools/bg-pause.test.ts +0 -61
- package/plugins/bizar/tests/tools/bg-report-progress.test.ts +0 -79
- package/plugins/bizar/tests/tools/bg-resume.test.ts +0 -40
- package/plugins/bizar/tests/tools/bg-send-message.test.ts +0 -116
- package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +0 -147
- package/plugins/bizar/tests/tools/bg-spawn-http.test.ts +0 -233
- package/plugins/bizar/tests/tools/bg-spawn.test.ts +0 -311
- package/plugins/bizar/tests/tools/bg-status.test.ts +0 -217
- package/plugins/bizar/tests/tools/cline-runner.test.ts +0 -115
- package/plugins/bizar/tests/tools/plan-action.test.ts +0 -599
- package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +0 -253
- package/plugins/bizar/tests/tools/sandbox.test.ts +0 -117
- package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +0 -390
- package/plugins/bizar/tests/update-deadlock.test.ts +0 -151
- package/plugins/bizar/tsconfig.json +0 -29
- package/scripts/mirror-agents-md.sh +0 -69
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* consensus/byzantine.ts — In-process PBFT-style consensus for Bizar.
|
|
3
|
+
*
|
|
4
|
+
* F-039 — thin port of ruflo's `v3/@claude-flow/swarm/src/consensus/byzantine.ts`.
|
|
5
|
+
* Bizar's review/decision steps need a 3-of-5 majority with view-change
|
|
6
|
+
* on faulty proposers, but not the full PBFT transport stack. This
|
|
7
|
+
* class is the in-memory consensus surface; it tracks one proposal
|
|
8
|
+
* at a time per (cluster, quorum) tuple, and exposes the four PBFT
|
|
9
|
+
* phases (`pre-prepare → prepare → commit → reply`) plus `view-change`.
|
|
10
|
+
*
|
|
11
|
+
* Design choices:
|
|
12
|
+
*
|
|
13
|
+
* • No network transport. Peers are passed in the constructor; the
|
|
14
|
+
* class is a single-node simulator that an outer orchestrator
|
|
15
|
+
* (or the MCP tool) drives by calling `onPrePrepare` / `onPrepare`
|
|
16
|
+
* with each peer's vote. This is exactly the surface Bizar needs
|
|
17
|
+
* for review-gate decisions; a real transport lives in a separate
|
|
18
|
+
* concern (would be `transport.ts` in ruflo — not ported).
|
|
19
|
+
*
|
|
20
|
+
* • Determinism. Quorum math is purely set-based. The only ordering
|
|
21
|
+
* input is the round-robin proposer election in `queen.ts`, and
|
|
22
|
+
* even that accepts an explicit `proposerSeed` so tests can pin
|
|
23
|
+
* the head.
|
|
24
|
+
*
|
|
25
|
+
* • Replay protection. `propose(payload)` is keyed on the payload's
|
|
26
|
+
* stable hash. Re-submitting the same payload returns the cached
|
|
27
|
+
* proposal id without re-running pre-prepare.
|
|
28
|
+
*
|
|
29
|
+
* • Fault detection. A "faulty" proposer is one that votes `no` in
|
|
30
|
+
* the prepare phase (or doesn't vote at all within the timeout).
|
|
31
|
+
* `viewChange(reason)` walks the round-robin schedule to the next
|
|
32
|
+
* peer and increments `viewNumber`.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
36
|
+
|
|
37
|
+
import { QueenCoordinator } from "./queen.js";
|
|
38
|
+
import {
|
|
39
|
+
DEFAULT_MAX_FAULTS,
|
|
40
|
+
DEFAULT_PEER_COUNT,
|
|
41
|
+
DEFAULT_QUORUM,
|
|
42
|
+
PHASE_ORDER,
|
|
43
|
+
type CommitResult,
|
|
44
|
+
type ConsensusOpts,
|
|
45
|
+
type ConsensusStatus,
|
|
46
|
+
type Decision,
|
|
47
|
+
type Phase,
|
|
48
|
+
type ProposeResult,
|
|
49
|
+
type Proposal,
|
|
50
|
+
type ProposalSnapshot,
|
|
51
|
+
type ProposalStatus,
|
|
52
|
+
type Vote,
|
|
53
|
+
type VoteResult,
|
|
54
|
+
type ViewChangeResult,
|
|
55
|
+
} from "./types.js";
|
|
56
|
+
|
|
57
|
+
// ─── Status derivation ─────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
function deriveStatus(p: Proposal, now: Date, timeoutMs: number): ProposalStatus {
|
|
60
|
+
if (p.phase === "reply" && p.committedAt) return "committed";
|
|
61
|
+
if (p.phase === "reply" && p.viewChangeReason) return "view-change";
|
|
62
|
+
if (p.phase === "reply" && !p.committedAt) return "rejected";
|
|
63
|
+
if (p.phase === "commit") return "commit";
|
|
64
|
+
if (p.phase === "prepare") {
|
|
65
|
+
if (now.getTime() - new Date(p.createdAt).getTime() > timeoutMs) return "expired";
|
|
66
|
+
if (p.rejections.size > 0) return "rejected";
|
|
67
|
+
return "prepare";
|
|
68
|
+
}
|
|
69
|
+
if (p.phase === "pre-prepare") {
|
|
70
|
+
if (now.getTime() - new Date(p.createdAt).getTime() > timeoutMs) return "expired";
|
|
71
|
+
return "pre-prepare";
|
|
72
|
+
}
|
|
73
|
+
return "pending";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function snapshotOf(p: Proposal, now: Date, timeoutMs: number): ProposalSnapshot {
|
|
77
|
+
return {
|
|
78
|
+
id: p.id,
|
|
79
|
+
status: deriveStatus(p, now, timeoutMs),
|
|
80
|
+
phase: p.phase,
|
|
81
|
+
proposer: p.proposer,
|
|
82
|
+
quorum: p.quorum,
|
|
83
|
+
peerCount: p.peerCount,
|
|
84
|
+
approvals: p.approvals.size,
|
|
85
|
+
rejections: p.rejections.size,
|
|
86
|
+
abstentions: p.abstentions.size,
|
|
87
|
+
createdAt: p.createdAt,
|
|
88
|
+
committedAt: p.committedAt,
|
|
89
|
+
finalizedAt: p.finalizedAt,
|
|
90
|
+
viewChangeReason: p.viewChangeReason,
|
|
91
|
+
payload: p.payload,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─── ByzantineConsensus class ──────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
export interface ByzantineConsensusOpts extends ConsensusOpts {
|
|
98
|
+
/** Wall-clock timeout (ms) after which a pending proposal expires.
|
|
99
|
+
* Defaults to 5 seconds. */
|
|
100
|
+
timeoutMs?: number;
|
|
101
|
+
/** Maximum number of proposals to retain in the history (LRU eviction).
|
|
102
|
+
* Defaults to 100. */
|
|
103
|
+
historyLimit?: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* In-process Byzantine fault-tolerant consensus. One instance per
|
|
108
|
+
* (cluster, quorum) tuple. Thread-unsafe — single-threaded JS so
|
|
109
|
+
* the concern is theoretical, but document it for callers that wrap
|
|
110
|
+
* the instance in worker threads.
|
|
111
|
+
*/
|
|
112
|
+
export class ByzantineConsensus {
|
|
113
|
+
private readonly localAgentId: string;
|
|
114
|
+
private readonly peers: string[];
|
|
115
|
+
private readonly quorum: number;
|
|
116
|
+
private readonly maxFaults: number;
|
|
117
|
+
private readonly timeoutMs: number;
|
|
118
|
+
private readonly historyLimit: number;
|
|
119
|
+
private readonly now: () => Date;
|
|
120
|
+
|
|
121
|
+
private readonly queen: QueenCoordinator;
|
|
122
|
+
private viewNumber = 0;
|
|
123
|
+
|
|
124
|
+
/** Live proposals, keyed by proposalId. */
|
|
125
|
+
private readonly proposals = new Map<string, Proposal>();
|
|
126
|
+
/** Payload digest → proposalId, for replay protection. */
|
|
127
|
+
private readonly payloadIndex = new Map<string, string>();
|
|
128
|
+
/** Total counts (status === 'committed' / 'rejected' / 'expired'). */
|
|
129
|
+
private committed = 0;
|
|
130
|
+
private rejected = 0;
|
|
131
|
+
private expired = 0;
|
|
132
|
+
private viewChanges = 0;
|
|
133
|
+
|
|
134
|
+
constructor(opts: ByzantineConsensusOpts) {
|
|
135
|
+
if (!opts || typeof opts.localAgentId !== "string" || opts.localAgentId.trim().length === 0) {
|
|
136
|
+
throw new Error("ByzantineConsensus: `localAgentId` is required");
|
|
137
|
+
}
|
|
138
|
+
if (!Array.isArray(opts.peers) || opts.peers.length < 2) {
|
|
139
|
+
throw new Error("ByzantineConsensus: `peers` must be a non-empty array of >=2 ids");
|
|
140
|
+
}
|
|
141
|
+
if (!opts.peers.includes(opts.localAgentId)) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`ByzantineConsensus: localAgentId "${opts.localAgentId}" must be in the peer list`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
const peerCount = opts.peers.length;
|
|
147
|
+
const quorum = opts.quorum ?? (peerCount >= DEFAULT_PEER_COUNT
|
|
148
|
+
? DEFAULT_QUORUM
|
|
149
|
+
: Math.max(1, Math.floor(peerCount / 2) + 1));
|
|
150
|
+
if (quorum < 1 || quorum > peerCount) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`ByzantineConsensus: quorum (${quorum}) must be in [1, peerCount=${peerCount}]`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
const maxFaults = opts.maxFaults ?? DEFAULT_MAX_FAULTS;
|
|
156
|
+
// PBFT requires n >= 3f+1 for true Byzantine tolerance. We allow
|
|
157
|
+
// smaller clusters (the F-039 spec uses 3-of-5 simple majority,
|
|
158
|
+
// not full BFT), but warn at the API boundary so callers know.
|
|
159
|
+
if (peerCount < 3 * maxFaults + 1) {
|
|
160
|
+
// Best-effort — we don't throw because the orchestrator can
|
|
161
|
+
// still drive a useful simple-majority consensus on smaller
|
|
162
|
+
// clusters. The QueenCoordinator tracks the same `maxFaults`
|
|
163
|
+
// value for proposer-skip math.
|
|
164
|
+
void maxFaults; // surfaces in `this.maxFaults` getter below.
|
|
165
|
+
}
|
|
166
|
+
this.localAgentId = opts.localAgentId;
|
|
167
|
+
this.peers = [...opts.peers];
|
|
168
|
+
this.quorum = quorum;
|
|
169
|
+
this.maxFaults = maxFaults;
|
|
170
|
+
this.timeoutMs = opts.timeoutMs ?? 5_000;
|
|
171
|
+
this.historyLimit = opts.historyLimit ?? 100;
|
|
172
|
+
this.now = opts.now ?? (() => new Date());
|
|
173
|
+
this.queen = new QueenCoordinator({
|
|
174
|
+
peers: this.peers,
|
|
175
|
+
maxFaults: this.maxFaults,
|
|
176
|
+
seed: opts.proposerSeed,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ─── Read-only accessors ───────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
/** Identity of the local agent (mirrors `opts.localAgentId`). */
|
|
183
|
+
getLocalAgentId(): string {
|
|
184
|
+
return this.localAgentId;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Maximum Byzantine faults the cluster is sized for. */
|
|
188
|
+
getMaxFaults(): number {
|
|
189
|
+
return this.maxFaults;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Maximum proposal history retained (LRU eviction). */
|
|
193
|
+
getHistoryLimit(): number {
|
|
194
|
+
return this.historyLimit;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Current proposer (head of the round-robin schedule). */
|
|
198
|
+
getCurrentProposer(): string {
|
|
199
|
+
return this.queen.currentProposer();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
getViewNumber(): number {
|
|
203
|
+
return this.viewNumber;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
getQuorum(): number {
|
|
207
|
+
return this.quorum;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
getPeers(): string[] {
|
|
211
|
+
return [...this.peers];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ─── Proposal lifecycle ─────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Initiate pre-prepare for a new proposal. Generates a stable
|
|
218
|
+
* proposalId from the payload + viewNumber. If the same payload is
|
|
219
|
+
* re-submitted in the same view, the existing proposal is returned
|
|
220
|
+
* (replay protection — see F-039 spec).
|
|
221
|
+
*/
|
|
222
|
+
propose(payload: unknown): ProposeResult {
|
|
223
|
+
this.evictExpired();
|
|
224
|
+
const digest = this.payloadDigest(payload, this.viewNumber);
|
|
225
|
+
const existingId = this.payloadIndex.get(digest);
|
|
226
|
+
if (existingId) {
|
|
227
|
+
const existing = this.proposals.get(existingId);
|
|
228
|
+
if (existing) {
|
|
229
|
+
return {
|
|
230
|
+
proposalId: existing.id,
|
|
231
|
+
status: deriveStatus(existing, this.now(), this.timeoutMs),
|
|
232
|
+
phase: existing.phase,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const proposer = this.queen.currentProposer();
|
|
238
|
+
const id = `bft-v${this.viewNumber}-${randomUUID().slice(0, 8)}`;
|
|
239
|
+
const proposal: Proposal = {
|
|
240
|
+
id,
|
|
241
|
+
payload,
|
|
242
|
+
proposer,
|
|
243
|
+
quorum: this.quorum,
|
|
244
|
+
peerCount: this.peers.length,
|
|
245
|
+
phase: "pre-prepare",
|
|
246
|
+
approvals: new Set(),
|
|
247
|
+
rejections: new Set(),
|
|
248
|
+
abstentions: new Set(),
|
|
249
|
+
createdAt: this.now().toISOString(),
|
|
250
|
+
};
|
|
251
|
+
this.proposals.set(id, proposal);
|
|
252
|
+
this.payloadIndex.set(digest, id);
|
|
253
|
+
|
|
254
|
+
// Pre-prepare completes immediately for the local proposer; the
|
|
255
|
+
// proposer's own vote is added by `onPrepare` if the caller wants
|
|
256
|
+
// it counted. We do NOT auto-vote here — the caller decides whether
|
|
257
|
+
// the proposer participates in its own proposal (typical: yes).
|
|
258
|
+
this.advanceToPrepare(proposal);
|
|
259
|
+
|
|
260
|
+
return { proposalId: id, status: deriveStatus(proposal, this.now(), this.timeoutMs), phase: proposal.phase };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Cast a vote for a peer during the `prepare` phase. Returns the
|
|
265
|
+
* updated tally; if the proposal reaches quorum the phase auto-
|
|
266
|
+
* advances to `commit` and the `reply` snapshot is recorded.
|
|
267
|
+
*
|
|
268
|
+
* If the proposer themselves votes `no`, the proposal is
|
|
269
|
+
* immediately rejected (self-fault detection) — view-change is the
|
|
270
|
+
* caller's responsibility (call `viewChange`).
|
|
271
|
+
*/
|
|
272
|
+
castVote(proposalId: string, agentId: string, decision: Decision, signature?: string): VoteResult {
|
|
273
|
+
this.evictExpired();
|
|
274
|
+
const proposal = this.proposals.get(proposalId);
|
|
275
|
+
if (!proposal) {
|
|
276
|
+
throw new Error(`castVote: unknown proposalId: ${proposalId}`);
|
|
277
|
+
}
|
|
278
|
+
if (!this.peers.includes(agentId)) {
|
|
279
|
+
throw new Error(`castVote: agent "${agentId}" is not in the peer list`);
|
|
280
|
+
}
|
|
281
|
+
if (proposal.phase === "commit" || proposal.phase === "reply") {
|
|
282
|
+
// Late vote — already committed/rejected. Return current tally
|
|
283
|
+
// (matches ruflo's `vote` early-return when status !== 'pending').
|
|
284
|
+
return this.snapshotVoteResult(proposal);
|
|
285
|
+
}
|
|
286
|
+
if (proposal.phase !== "prepare") {
|
|
287
|
+
throw new Error(
|
|
288
|
+
`castVote: proposal ${proposalId} is in phase "${proposal.phase}", not "prepare"`,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Self-fault: proposer voting "no" is a Byzantine signal.
|
|
293
|
+
if (decision === "no" && agentId === proposal.proposer) {
|
|
294
|
+
proposal.rejections.add(agentId);
|
|
295
|
+
proposal.phase = "reply";
|
|
296
|
+
proposal.viewChangeReason = `proposer-${agentId}-self-rejected`;
|
|
297
|
+
proposal.finalizedAt = this.now().toISOString();
|
|
298
|
+
this.rejected++;
|
|
299
|
+
this.viewChanges++;
|
|
300
|
+
this.queen.recordFault(agentId);
|
|
301
|
+
// Advance the round-robin + view number so the next proposal
|
|
302
|
+
// lands on a non-faulted proposer.
|
|
303
|
+
this.queen.advance();
|
|
304
|
+
this.viewNumber++;
|
|
305
|
+
// Clear the payload digest so the same payload can be re-proposed
|
|
306
|
+
// under the new view.
|
|
307
|
+
const digest = this.payloadDigest(proposal.payload, this.viewNumber - 1);
|
|
308
|
+
if (this.payloadIndex.get(digest) === proposal.id) {
|
|
309
|
+
this.payloadIndex.delete(digest);
|
|
310
|
+
}
|
|
311
|
+
return this.snapshotVoteResult(proposal);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Idempotency: re-voting replaces the prior vote.
|
|
315
|
+
proposal.approvals.delete(agentId);
|
|
316
|
+
proposal.rejections.delete(agentId);
|
|
317
|
+
proposal.abstentions.delete(agentId);
|
|
318
|
+
if (decision === "yes") proposal.approvals.add(agentId);
|
|
319
|
+
else if (decision === "no") proposal.rejections.add(agentId);
|
|
320
|
+
else proposal.abstentions.add(agentId);
|
|
321
|
+
|
|
322
|
+
// Track the vote in the proposal's audit log (for `status()` output).
|
|
323
|
+
const vote: Vote = {
|
|
324
|
+
agentId,
|
|
325
|
+
phase: "prepare",
|
|
326
|
+
decision,
|
|
327
|
+
signature,
|
|
328
|
+
timestamp: this.now().toISOString(),
|
|
329
|
+
};
|
|
330
|
+
void vote; // vote is captured by the Set membership; kept in the
|
|
331
|
+
// interface for callers that want to log externally.
|
|
332
|
+
|
|
333
|
+
// Quorum check — promote on majority `yes`.
|
|
334
|
+
if (proposal.approvals.size >= proposal.quorum) {
|
|
335
|
+
proposal.phase = "commit";
|
|
336
|
+
proposal.committedAt = this.now().toISOString();
|
|
337
|
+
// Auto-advance to reply immediately (synchronous thin port).
|
|
338
|
+
proposal.phase = "reply";
|
|
339
|
+
proposal.finalizedAt = this.now().toISOString();
|
|
340
|
+
this.committed++;
|
|
341
|
+
} else if (proposal.rejections.size > 0 && !this.canStillReachQuorum(proposal)) {
|
|
342
|
+
proposal.phase = "reply";
|
|
343
|
+
proposal.finalizedAt = this.now().toISOString();
|
|
344
|
+
this.rejected++;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return this.snapshotVoteResult(proposal);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* External entry-point that lets the orchestrator deliver a peer
|
|
352
|
+
* vote that arrived over a transport. The `proposalId` is passed
|
|
353
|
+
* explicitly (it's not part of the `Vote` shape — Votes are scoped
|
|
354
|
+
* to a single proposal inside the cluster).
|
|
355
|
+
*/
|
|
356
|
+
onPrepare(proposalId: string, vote: Vote): VoteResult {
|
|
357
|
+
return this.castVote(proposalId, vote.agentId, vote.decision, vote.signature);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* External entry-point for a peer that has accepted the
|
|
362
|
+
* pre-prepare. In the in-memory thin port this is mostly a
|
|
363
|
+
* confirmation that the peer transitioned to `prepare` — we
|
|
364
|
+
* record the pre-prepare accept and let `onPrepare` / `castVote`
|
|
365
|
+
* carry the actual vote.
|
|
366
|
+
*/
|
|
367
|
+
onPrePrepare(peerId: string, proposalId: string): { proposalId: string; phase: Phase } {
|
|
368
|
+
const proposal = this.proposals.get(proposalId);
|
|
369
|
+
if (!proposal) {
|
|
370
|
+
throw new Error(`onPrePrepare: unknown proposalId: ${proposalId}`);
|
|
371
|
+
}
|
|
372
|
+
if (!this.peers.includes(peerId)) {
|
|
373
|
+
throw new Error(`onPrePrepare: peer "${peerId}" is not in the peer list`);
|
|
374
|
+
}
|
|
375
|
+
if (proposal.phase !== "pre-prepare" && proposal.phase !== "prepare") {
|
|
376
|
+
throw new Error(
|
|
377
|
+
`onPrePrepare: proposal ${proposalId} is past pre-prepare (phase "${proposal.phase}")`,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
// Pre-prepare → prepare happens automatically in `propose`; this
|
|
381
|
+
// method exists to satisfy the spec surface and let the orchestrator
|
|
382
|
+
// record that peerId acknowledged the pre-prepare.
|
|
383
|
+
return { proposalId, phase: proposal.phase };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Mark a proposal as committed (used when the orchestrator wants
|
|
388
|
+
* to force a commit, e.g. for tests that skip the vote tally).
|
|
389
|
+
*/
|
|
390
|
+
commit(proposalId: string): CommitResult {
|
|
391
|
+
const proposal = this.proposals.get(proposalId);
|
|
392
|
+
if (!proposal) {
|
|
393
|
+
throw new Error(`commit: unknown proposalId: ${proposalId}`);
|
|
394
|
+
}
|
|
395
|
+
proposal.phase = "commit";
|
|
396
|
+
proposal.committedAt = this.now().toISOString();
|
|
397
|
+
proposal.phase = "reply";
|
|
398
|
+
proposal.finalizedAt = this.now().toISOString();
|
|
399
|
+
this.committed++;
|
|
400
|
+
return {
|
|
401
|
+
proposalId,
|
|
402
|
+
status: "committed",
|
|
403
|
+
committedAt: proposal.committedAt,
|
|
404
|
+
finalizedAt: proposal.finalizedAt,
|
|
405
|
+
approvals: proposal.approvals.size,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Trigger a view-change. Advances the round-robin schedule to the
|
|
411
|
+
* next eligible peer (lowest fault count), increments viewNumber,
|
|
412
|
+
* and increments the viewChanges counter. The currently-active
|
|
413
|
+
* proposal (if any) is marked with the given `reason` and frozen
|
|
414
|
+
* at its current phase — the orchestrator can re-submit the same
|
|
415
|
+
* payload under the new view.
|
|
416
|
+
*/
|
|
417
|
+
viewChange(reason: string, proposalId?: string): ViewChangeResult {
|
|
418
|
+
const previousProposer = this.queen.currentProposer();
|
|
419
|
+
this.queen.recordFault(previousProposer);
|
|
420
|
+
this.queen.advance();
|
|
421
|
+
this.viewNumber++;
|
|
422
|
+
this.viewChanges++;
|
|
423
|
+
|
|
424
|
+
let frozenProposalId = proposalId ?? "";
|
|
425
|
+
if (frozenProposalId) {
|
|
426
|
+
const proposal = this.proposals.get(frozenProposalId);
|
|
427
|
+
if (proposal) {
|
|
428
|
+
proposal.viewChangeReason = reason;
|
|
429
|
+
if (proposal.phase !== "reply") {
|
|
430
|
+
proposal.phase = "reply";
|
|
431
|
+
proposal.finalizedAt = this.now().toISOString();
|
|
432
|
+
// A view-change counts as neither committed nor rejected —
|
|
433
|
+
// the new view will re-propose the payload.
|
|
434
|
+
if (proposal.approvals.size < proposal.quorum) {
|
|
435
|
+
this.rejected++;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
// Bump the payload digest index so the new view produces a
|
|
439
|
+
// fresh proposalId for the same payload (otherwise replay
|
|
440
|
+
// protection would short-circuit the retry).
|
|
441
|
+
const digest = this.payloadDigest(proposal.payload, this.viewNumber - 1);
|
|
442
|
+
if (this.payloadIndex.get(digest) === proposal.id) {
|
|
443
|
+
this.payloadIndex.delete(digest);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return {
|
|
449
|
+
previousProposer,
|
|
450
|
+
newProposer: this.queen.currentProposer(),
|
|
451
|
+
viewNumber: this.viewNumber,
|
|
452
|
+
reason,
|
|
453
|
+
proposalId: frozenProposalId,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// ─── Status / snapshot ─────────────────────────────────────────────
|
|
458
|
+
|
|
459
|
+
/** Read-only snapshot of every proposal the instance has tracked. */
|
|
460
|
+
status(): ConsensusStatus {
|
|
461
|
+
this.evictExpired();
|
|
462
|
+
const snapshots: ProposalSnapshot[] = [];
|
|
463
|
+
for (const p of this.proposals.values()) {
|
|
464
|
+
snapshots.push(snapshotOf(p, this.now(), this.timeoutMs));
|
|
465
|
+
}
|
|
466
|
+
// Most-recent first (lex order by createdAt desc is fine for in-memory).
|
|
467
|
+
snapshots.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
468
|
+
return {
|
|
469
|
+
proposals: snapshots,
|
|
470
|
+
currentProposer: this.queen.currentProposer(),
|
|
471
|
+
viewNumber: this.viewNumber,
|
|
472
|
+
committed: this.committed,
|
|
473
|
+
rejected: this.rejected,
|
|
474
|
+
expired: this.expired,
|
|
475
|
+
viewChanges: this.viewChanges,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** Snapshot of a single proposal, or `undefined` if unknown. */
|
|
480
|
+
getProposal(proposalId: string): ProposalSnapshot | undefined {
|
|
481
|
+
const p = this.proposals.get(proposalId);
|
|
482
|
+
if (!p) return undefined;
|
|
483
|
+
return snapshotOf(p, this.now(), this.timeoutMs);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// ─── Helpers ────────────────────────────────────────────────────────
|
|
487
|
+
|
|
488
|
+
private snapshotVoteResult(proposal: Proposal): VoteResult {
|
|
489
|
+
return {
|
|
490
|
+
proposalId: proposal.id,
|
|
491
|
+
status: deriveStatus(proposal, this.now(), this.timeoutMs),
|
|
492
|
+
phase: proposal.phase,
|
|
493
|
+
approvals: proposal.approvals.size,
|
|
494
|
+
rejections: proposal.rejections.size,
|
|
495
|
+
abstentions: proposal.abstentions.size,
|
|
496
|
+
committed: proposal.phase === "reply" && !!proposal.committedAt,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Promote a proposal from `pre-prepare` to `prepare`. In the thin
|
|
502
|
+
* port this is synchronous — we don't simulate network latency.
|
|
503
|
+
*/
|
|
504
|
+
private advanceToPrepare(proposal: Proposal): void {
|
|
505
|
+
if (proposal.phase !== "pre-prepare") return;
|
|
506
|
+
proposal.phase = "prepare";
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Returns true if a proposal in `prepare` can still reach quorum
|
|
511
|
+
* given the remaining un-voted peers. Used to short-circuit
|
|
512
|
+
* dead proposals into `rejected` early.
|
|
513
|
+
*/
|
|
514
|
+
private canStillReachQuorum(proposal: Proposal): boolean {
|
|
515
|
+
const voted = proposal.approvals.size + proposal.rejections.size + proposal.abstentions.size;
|
|
516
|
+
const remaining = proposal.peerCount - voted;
|
|
517
|
+
const maxYes = proposal.approvals.size + remaining;
|
|
518
|
+
return maxYes >= proposal.quorum;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Walk the proposal map, mark anything older than `timeoutMs` as
|
|
523
|
+
* expired, and trim history to `historyLimit` entries.
|
|
524
|
+
*/
|
|
525
|
+
private evictExpired(): void {
|
|
526
|
+
const now = this.now();
|
|
527
|
+
const toExpire: string[] = [];
|
|
528
|
+
for (const [id, p] of this.proposals) {
|
|
529
|
+
if (p.phase === "reply") continue;
|
|
530
|
+
if (now.getTime() - new Date(p.createdAt).getTime() > this.timeoutMs) {
|
|
531
|
+
toExpire.push(id);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
for (const id of toExpire) {
|
|
535
|
+
const p = this.proposals.get(id);
|
|
536
|
+
if (!p) continue;
|
|
537
|
+
p.phase = "reply";
|
|
538
|
+
p.finalizedAt = now.toISOString();
|
|
539
|
+
// Strip the payload digest so a future re-submit gets a fresh
|
|
540
|
+
// proposalId under the same view.
|
|
541
|
+
const digest = this.payloadDigest(p.payload, this.viewNumber);
|
|
542
|
+
if (this.payloadIndex.get(digest) === id) {
|
|
543
|
+
this.payloadIndex.delete(digest);
|
|
544
|
+
}
|
|
545
|
+
this.expired++;
|
|
546
|
+
}
|
|
547
|
+
// LRU trim.
|
|
548
|
+
if (this.proposals.size > this.historyLimit) {
|
|
549
|
+
const ordered = Array.from(this.proposals.values())
|
|
550
|
+
.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
551
|
+
const drop = ordered.slice(0, this.proposals.size - this.historyLimit);
|
|
552
|
+
for (const p of drop) {
|
|
553
|
+
this.proposals.delete(p.id);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Stable hash of a payload for replay protection. The viewNumber is
|
|
560
|
+
* included so the same payload in a new view produces a fresh digest
|
|
561
|
+
* (view-changes are explicitly allowed to re-propose).
|
|
562
|
+
*/
|
|
563
|
+
private payloadDigest(payload: unknown, viewNumber: number): string {
|
|
564
|
+
return createHash("sha256")
|
|
565
|
+
.update(JSON.stringify(payload ?? null))
|
|
566
|
+
.update(`|v${viewNumber}`)
|
|
567
|
+
.digest("hex");
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// ─── Re-exports for callers that want them in one import ───────────────
|
|
572
|
+
|
|
573
|
+
export type {
|
|
574
|
+
CommitResult,
|
|
575
|
+
ConsensusOpts,
|
|
576
|
+
ConsensusStatus,
|
|
577
|
+
Decision,
|
|
578
|
+
Phase,
|
|
579
|
+
ProposeResult,
|
|
580
|
+
Proposal,
|
|
581
|
+
ProposalSnapshot,
|
|
582
|
+
ProposalStatus,
|
|
583
|
+
Vote,
|
|
584
|
+
VoteResult,
|
|
585
|
+
ViewChangeResult,
|
|
586
|
+
};
|
|
587
|
+
export { PHASE_ORDER };
|