magi-ai 0.1.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/LICENSE +21 -0
- package/README.ja.md +377 -0
- package/README.md +377 -0
- package/dist/bin/magi-benchmark.d.ts +14 -0
- package/dist/bin/magi-benchmark.js +93 -0
- package/dist/bin/magi-mcp.d.ts +8 -0
- package/dist/bin/magi-mcp.js +28 -0
- package/dist/bin/magi.d.ts +2 -0
- package/dist/bin/magi.js +634 -0
- package/dist/src/adapters/base.d.ts +34 -0
- package/dist/src/adapters/base.js +149 -0
- package/dist/src/adapters/claude.d.ts +29 -0
- package/dist/src/adapters/claude.js +65 -0
- package/dist/src/adapters/codex.d.ts +21 -0
- package/dist/src/adapters/codex.js +41 -0
- package/dist/src/adapters/gemini.d.ts +18 -0
- package/dist/src/adapters/gemini.js +31 -0
- package/dist/src/adapters/registry.d.ts +19 -0
- package/dist/src/adapters/registry.js +59 -0
- package/dist/src/audit/hash-chain.d.ts +21 -0
- package/dist/src/audit/hash-chain.js +70 -0
- package/dist/src/audit/types.d.ts +25 -0
- package/dist/src/audit/types.js +1 -0
- package/dist/src/audit/writer.d.ts +18 -0
- package/dist/src/audit/writer.js +100 -0
- package/dist/src/benchmark/golden-tasks.d.ts +9 -0
- package/dist/src/benchmark/golden-tasks.js +476 -0
- package/dist/src/benchmark/reporter.d.ts +5 -0
- package/dist/src/benchmark/reporter.js +107 -0
- package/dist/src/benchmark/runner.d.ts +30 -0
- package/dist/src/benchmark/runner.js +224 -0
- package/dist/src/benchmark/scorer.d.ts +12 -0
- package/dist/src/benchmark/scorer.js +124 -0
- package/dist/src/benchmark/types.d.ts +54 -0
- package/dist/src/benchmark/types.js +1 -0
- package/dist/src/cache/deliberation-cache.d.ts +49 -0
- package/dist/src/cache/deliberation-cache.js +127 -0
- package/dist/src/cli/commands/config-cmd.d.ts +11 -0
- package/dist/src/cli/commands/config-cmd.js +190 -0
- package/dist/src/cli/commands/demo.d.ts +12 -0
- package/dist/src/cli/commands/demo.js +66 -0
- package/dist/src/cli/commands/setup.d.ts +7 -0
- package/dist/src/cli/commands/setup.js +182 -0
- package/dist/src/cli/i18n.d.ts +89 -0
- package/dist/src/cli/i18n.js +176 -0
- package/dist/src/cli/interactive-select.d.ts +27 -0
- package/dist/src/cli/interactive-select.js +130 -0
- package/dist/src/cli/tui-setup.d.ts +24 -0
- package/dist/src/cli/tui-setup.js +42 -0
- package/dist/src/config/cli-detector.d.ts +37 -0
- package/dist/src/config/cli-detector.js +99 -0
- package/dist/src/config/user-config.d.ts +81 -0
- package/dist/src/config/user-config.js +134 -0
- package/dist/src/context/auto-collector.d.ts +43 -0
- package/dist/src/context/auto-collector.js +337 -0
- package/dist/src/context/manager.d.ts +35 -0
- package/dist/src/context/manager.js +162 -0
- package/dist/src/context/serializer.d.ts +20 -0
- package/dist/src/context/serializer.js +52 -0
- package/dist/src/demo/recorded-deliberation.d.ts +13 -0
- package/dist/src/demo/recorded-deliberation.js +277 -0
- package/dist/src/engine/angel-detector.d.ts +83 -0
- package/dist/src/engine/angel-detector.js +334 -0
- package/dist/src/engine/at-field.d.ts +40 -0
- package/dist/src/engine/at-field.js +195 -0
- package/dist/src/engine/berserk-orchestrator.d.ts +66 -0
- package/dist/src/engine/berserk-orchestrator.js +378 -0
- package/dist/src/engine/change-metrics.d.ts +56 -0
- package/dist/src/engine/change-metrics.js +214 -0
- package/dist/src/engine/consensus.d.ts +20 -0
- package/dist/src/engine/consensus.js +146 -0
- package/dist/src/engine/dead-sea-scrolls.d.ts +132 -0
- package/dist/src/engine/dead-sea-scrolls.js +610 -0
- package/dist/src/engine/drift-detector.d.ts +39 -0
- package/dist/src/engine/drift-detector.js +225 -0
- package/dist/src/engine/dummy-plug.d.ts +44 -0
- package/dist/src/engine/dummy-plug.js +190 -0
- package/dist/src/engine/engram-manager.d.ts +55 -0
- package/dist/src/engine/engram-manager.js +306 -0
- package/dist/src/engine/events.d.ts +130 -0
- package/dist/src/engine/events.js +44 -0
- package/dist/src/engine/gospel.d.ts +30 -0
- package/dist/src/engine/gospel.js +129 -0
- package/dist/src/engine/hallucination-detector.d.ts +33 -0
- package/dist/src/engine/hallucination-detector.js +215 -0
- package/dist/src/engine/human-resolver.d.ts +19 -0
- package/dist/src/engine/human-resolver.js +89 -0
- package/dist/src/engine/instrumentality.d.ts +64 -0
- package/dist/src/engine/instrumentality.js +297 -0
- package/dist/src/engine/iruel-battle.d.ts +79 -0
- package/dist/src/engine/iruel-battle.js +319 -0
- package/dist/src/engine/kernel/deliberation-kernel.d.ts +12 -0
- package/dist/src/engine/kernel/deliberation-kernel.js +303 -0
- package/dist/src/engine/kernel/index.d.ts +8 -0
- package/dist/src/engine/kernel/index.js +7 -0
- package/dist/src/engine/kernel/phase-runner.d.ts +10 -0
- package/dist/src/engine/kernel/phase-runner.js +155 -0
- package/dist/src/engine/kernel/post-processor.d.ts +17 -0
- package/dist/src/engine/kernel/post-processor.js +131 -0
- package/dist/src/engine/kernel/types.d.ts +107 -0
- package/dist/src/engine/kernel/types.js +1 -0
- package/dist/src/engine/kernel/unit-executor.d.ts +6 -0
- package/dist/src/engine/kernel/unit-executor.js +132 -0
- package/dist/src/engine/lcl-manager.d.ts +44 -0
- package/dist/src/engine/lcl-manager.js +143 -0
- package/dist/src/engine/middleware/cache.d.ts +7 -0
- package/dist/src/engine/middleware/cache.js +29 -0
- package/dist/src/engine/middleware/chain.d.ts +18 -0
- package/dist/src/engine/middleware/chain.js +45 -0
- package/dist/src/engine/middleware/firewall.d.ts +8 -0
- package/dist/src/engine/middleware/firewall.js +24 -0
- package/dist/src/engine/middleware/index.d.ts +4 -0
- package/dist/src/engine/middleware/index.js +3 -0
- package/dist/src/engine/middleware/types.d.ts +43 -0
- package/dist/src/engine/middleware/types.js +1 -0
- package/dist/src/engine/nebuchadnezzar-key.d.ts +61 -0
- package/dist/src/engine/nebuchadnezzar-key.js +203 -0
- package/dist/src/engine/neon-genesis.d.ts +52 -0
- package/dist/src/engine/neon-genesis.js +203 -0
- package/dist/src/engine/objective-judge.d.ts +53 -0
- package/dist/src/engine/objective-judge.js +214 -0
- package/dist/src/engine/offline-mode.d.ts +18 -0
- package/dist/src/engine/offline-mode.js +46 -0
- package/dist/src/engine/orchestrator.d.ts +79 -0
- package/dist/src/engine/orchestrator.js +58 -0
- package/dist/src/engine/secret-cipher.d.ts +26 -0
- package/dist/src/engine/secret-cipher.js +114 -0
- package/dist/src/engine/seele-council.d.ts +90 -0
- package/dist/src/engine/seele-council.js +482 -0
- package/dist/src/engine/self-destruct.d.ts +61 -0
- package/dist/src/engine/self-destruct.js +231 -0
- package/dist/src/engine/self-evolution.d.ts +64 -0
- package/dist/src/engine/self-evolution.js +368 -0
- package/dist/src/engine/sync-rate.d.ts +45 -0
- package/dist/src/engine/sync-rate.js +151 -0
- package/dist/src/engine/type666-firewall.d.ts +76 -0
- package/dist/src/engine/type666-firewall.js +343 -0
- package/dist/src/engine/umbilical-cable.d.ts +41 -0
- package/dist/src/engine/umbilical-cable.js +192 -0
- package/dist/src/index.d.ts +106 -0
- package/dist/src/index.js +426 -0
- package/dist/src/mcp/server.d.ts +38 -0
- package/dist/src/mcp/server.js +196 -0
- package/dist/src/metrics/token-tracker.d.ts +38 -0
- package/dist/src/metrics/token-tracker.js +112 -0
- package/dist/src/parsers/json-extractor.d.ts +9 -0
- package/dist/src/parsers/json-extractor.js +239 -0
- package/dist/src/parsers/opinion-schema.d.ts +81 -0
- package/dist/src/parsers/opinion-schema.js +147 -0
- package/dist/src/parsers/unstructured-parser.d.ts +20 -0
- package/dist/src/parsers/unstructured-parser.js +122 -0
- package/dist/src/pipelines/architecture.d.ts +10 -0
- package/dist/src/pipelines/architecture.js +9 -0
- package/dist/src/pipelines/bug-analysis.d.ts +9 -0
- package/dist/src/pipelines/bug-analysis.js +8 -0
- package/dist/src/pipelines/code-review.d.ts +10 -0
- package/dist/src/pipelines/code-review.js +30 -0
- package/dist/src/pipelines/custom.d.ts +14 -0
- package/dist/src/pipelines/custom.js +29 -0
- package/dist/src/pipelines/registry.d.ts +9 -0
- package/dist/src/pipelines/registry.js +20 -0
- package/dist/src/prompts/personas.d.ts +6 -0
- package/dist/src/prompts/personas.js +44 -0
- package/dist/src/prompts/schemas.d.ts +4 -0
- package/dist/src/prompts/schemas.js +24 -0
- package/dist/src/prompts/templates.d.ts +6 -0
- package/dist/src/prompts/templates.js +91 -0
- package/dist/src/repl/accessibility.d.ts +23 -0
- package/dist/src/repl/accessibility.js +46 -0
- package/dist/src/repl/banner.d.ts +4 -0
- package/dist/src/repl/banner.js +28 -0
- package/dist/src/repl/boot-animation.d.ts +13 -0
- package/dist/src/repl/boot-animation.js +143 -0
- package/dist/src/repl/completer.d.ts +21 -0
- package/dist/src/repl/completer.js +168 -0
- package/dist/src/repl/context.d.ts +24 -0
- package/dist/src/repl/context.js +42 -0
- package/dist/src/repl/display-utils.d.ts +13 -0
- package/dist/src/repl/display-utils.js +65 -0
- package/dist/src/repl/event-listener.d.ts +18 -0
- package/dist/src/repl/event-listener.js +112 -0
- package/dist/src/repl/export-formatter.d.ts +8 -0
- package/dist/src/repl/export-formatter.js +73 -0
- package/dist/src/repl/ghost-text.d.ts +31 -0
- package/dist/src/repl/ghost-text.js +119 -0
- package/dist/src/repl/handoff-animation.d.ts +15 -0
- package/dist/src/repl/handoff-animation.js +65 -0
- package/dist/src/repl/history.d.ts +16 -0
- package/dist/src/repl/history.js +130 -0
- package/dist/src/repl/job-registry.d.ts +26 -0
- package/dist/src/repl/job-registry.js +80 -0
- package/dist/src/repl/magi-repl.d.ts +72 -0
- package/dist/src/repl/magi-repl.js +1008 -0
- package/dist/src/repl/multiline-input.d.ts +45 -0
- package/dist/src/repl/multiline-input.js +78 -0
- package/dist/src/repl/prompt-builder.d.ts +19 -0
- package/dist/src/repl/prompt-builder.js +36 -0
- package/dist/src/repl/repl-state.d.ts +5 -0
- package/dist/src/repl/repl-state.js +19 -0
- package/dist/src/repl/result-display.d.ts +8 -0
- package/dist/src/repl/result-display.js +195 -0
- package/dist/src/repl/session-stats.d.ts +26 -0
- package/dist/src/repl/session-stats.js +119 -0
- package/dist/src/repl/slash-commands.d.ts +60 -0
- package/dist/src/repl/slash-commands.js +725 -0
- package/dist/src/repl/terminal-sanitize.d.ts +14 -0
- package/dist/src/repl/terminal-sanitize.js +19 -0
- package/dist/src/reporters/console.d.ts +7 -0
- package/dist/src/reporters/console.js +78 -0
- package/dist/src/reporters/json.d.ts +2 -0
- package/dist/src/reporters/json.js +3 -0
- package/dist/src/reporters/markdown.d.ts +2 -0
- package/dist/src/reporters/markdown.js +65 -0
- package/dist/src/reporters/streaming.d.ts +20 -0
- package/dist/src/reporters/streaming.js +178 -0
- package/dist/src/tui/activity-log.d.ts +23 -0
- package/dist/src/tui/activity-log.js +67 -0
- package/dist/src/tui/animations.d.ts +39 -0
- package/dist/src/tui/animations.js +167 -0
- package/dist/src/tui/ansi.d.ts +28 -0
- package/dist/src/tui/ansi.js +51 -0
- package/dist/src/tui/boot-sequence.d.ts +11 -0
- package/dist/src/tui/boot-sequence.js +98 -0
- package/dist/src/tui/colors.d.ts +101 -0
- package/dist/src/tui/colors.js +71 -0
- package/dist/src/tui/header.d.ts +24 -0
- package/dist/src/tui/header.js +122 -0
- package/dist/src/tui/index.d.ts +3 -0
- package/dist/src/tui/index.js +3 -0
- package/dist/src/tui/keypress.d.ts +25 -0
- package/dist/src/tui/keypress.js +95 -0
- package/dist/src/tui/layout.d.ts +74 -0
- package/dist/src/tui/layout.js +171 -0
- package/dist/src/tui/magi-tui.d.ts +101 -0
- package/dist/src/tui/magi-tui.js +754 -0
- package/dist/src/tui/panel.d.ts +45 -0
- package/dist/src/tui/panel.js +292 -0
- package/dist/src/tui/screen-buffer.d.ts +54 -0
- package/dist/src/tui/screen-buffer.js +262 -0
- package/dist/src/tui/status-bar.d.ts +25 -0
- package/dist/src/tui/status-bar.js +124 -0
- package/dist/src/tui/terminal-detect.d.ts +26 -0
- package/dist/src/tui/terminal-detect.js +44 -0
- package/dist/src/tui/tui-helpers.d.ts +12 -0
- package/dist/src/tui/tui-helpers.js +37 -0
- package/dist/src/types/adapter.d.ts +75 -0
- package/dist/src/types/adapter.js +36 -0
- package/dist/src/types/config.d.ts +108 -0
- package/dist/src/types/config.js +85 -0
- package/dist/src/types/consensus.d.ts +55 -0
- package/dist/src/types/consensus.js +17 -0
- package/dist/src/types/core.d.ts +178 -0
- package/dist/src/types/core.js +85 -0
- package/dist/src/types/magi-api.d.ts +62 -0
- package/dist/src/types/magi-api.js +7 -0
- package/dist/src/types/phase-h.d.ts +142 -0
- package/dist/src/types/phase-h.js +7 -0
- package/dist/src/types/phase-i.d.ts +186 -0
- package/dist/src/types/phase-i.js +6 -0
- package/dist/src/types/phase-k.d.ts +259 -0
- package/dist/src/types/phase-k.js +6 -0
- package/dist/src/types/phase-l.d.ts +199 -0
- package/dist/src/types/phase-l.js +6 -0
- package/dist/src/types/pipeline.d.ts +37 -0
- package/dist/src/types/pipeline.js +2 -0
- package/dist/src/utils/abstain-factory.d.ts +2 -0
- package/dist/src/utils/abstain-factory.js +18 -0
- package/dist/src/utils/errors.d.ts +34 -0
- package/dist/src/utils/errors.js +59 -0
- package/dist/src/utils/file-validator.d.ts +50 -0
- package/dist/src/utils/file-validator.js +124 -0
- package/dist/src/utils/fire-and-forget.d.ts +5 -0
- package/dist/src/utils/fire-and-forget.js +10 -0
- package/dist/src/utils/flag-validator.d.ts +21 -0
- package/dist/src/utils/flag-validator.js +79 -0
- package/dist/src/utils/freeze.d.ts +8 -0
- package/dist/src/utils/freeze.js +16 -0
- package/dist/src/utils/language-detector.d.ts +16 -0
- package/dist/src/utils/language-detector.js +159 -0
- package/dist/src/utils/latency-tracker.d.ts +45 -0
- package/dist/src/utils/latency-tracker.js +100 -0
- package/dist/src/utils/logger.d.ts +33 -0
- package/dist/src/utils/logger.js +112 -0
- package/dist/src/utils/process.d.ts +40 -0
- package/dist/src/utils/process.js +253 -0
- package/dist/src/utils/retry.d.ts +12 -0
- package/dist/src/utils/retry.js +30 -0
- package/dist/src/utils/safe-fs.d.ts +38 -0
- package/dist/src/utils/safe-fs.js +56 -0
- package/dist/src/utils/safe-json-parse.d.ts +15 -0
- package/dist/src/utils/safe-json-parse.js +49 -0
- package/dist/src/utils/sanitize.d.ts +14 -0
- package/dist/src/utils/sanitize.js +186 -0
- package/dist/src/utils/semaphore.d.ts +22 -0
- package/dist/src/utils/semaphore.js +57 -0
- package/dist/src/utils/shutdown.d.ts +6 -0
- package/dist/src/utils/shutdown.js +51 -0
- package/dist/src/utils/tty.d.ts +5 -0
- package/dist/src/utils/tty.js +7 -0
- package/package.json +82 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A-05 補完計画 (Instrumentality) — Deadlock Fusion Engine
|
|
3
|
+
*
|
|
4
|
+
* When deliberation reaches a deadlock after 3+ rounds, this engine
|
|
5
|
+
* fuses all unit opinions into a single synthesized assessment.
|
|
6
|
+
*
|
|
7
|
+
* The fusion process:
|
|
8
|
+
* 1. Detect deadlock conditions (shouldTrigger)
|
|
9
|
+
* 2. Build a weighted fusion prompt from all opinions
|
|
10
|
+
* 3. Execute via the primary adapter (MELCHIOR)
|
|
11
|
+
* 4. Parse and validate the fused opinion
|
|
12
|
+
* 5. Apply confidence discount (0.85x) since the opinion is derived
|
|
13
|
+
*
|
|
14
|
+
* Emits: 'instrumentality:triggered', 'fusion:complete'
|
|
15
|
+
*/
|
|
16
|
+
import { SafeOpinionSchema } from '../parsers/opinion-schema.js';
|
|
17
|
+
import { extractJson } from '../parsers/json-extractor.js';
|
|
18
|
+
import { estimateTokens } from '../metrics/token-tracker.js';
|
|
19
|
+
import { createAbstainOpinion } from '../utils/abstain-factory.js';
|
|
20
|
+
import { logger } from '../utils/logger.js';
|
|
21
|
+
import { OPINION_JSON_SCHEMA } from '../types/pipeline.js';
|
|
22
|
+
// ── Constants ────────────────────────────────────────────────────
|
|
23
|
+
/** Default fusion weights for the classic 3-body configuration */
|
|
24
|
+
const DEFAULT_FUSION_WEIGHTS = {
|
|
25
|
+
MELCHIOR: 0.40,
|
|
26
|
+
BALTHASAR: 0.35,
|
|
27
|
+
CASPER: 0.25,
|
|
28
|
+
};
|
|
29
|
+
/** Confidence discount applied to fused opinions (derived, not direct) */
|
|
30
|
+
const CONFIDENCE_DISCOUNT = 0.85;
|
|
31
|
+
/** Minimum rounds before Instrumentality can trigger */
|
|
32
|
+
const MIN_ROUNDS_FOR_TRIGGER = 3;
|
|
33
|
+
// ── InstrumentalityEngine ────────────────────────────────────────
|
|
34
|
+
export class InstrumentalityEngine {
|
|
35
|
+
adapters;
|
|
36
|
+
eventBus;
|
|
37
|
+
constructor(adapters, eventBus) {
|
|
38
|
+
this.adapters = adapters;
|
|
39
|
+
this.eventBus = eventBus;
|
|
40
|
+
}
|
|
41
|
+
// ── Trigger Detection ──────────────────────────────────────────
|
|
42
|
+
/**
|
|
43
|
+
* Determine whether Instrumentality should trigger.
|
|
44
|
+
*
|
|
45
|
+
* Conditions:
|
|
46
|
+
* - consensusDecision is 'DEADLOCK'
|
|
47
|
+
* - At least MIN_ROUNDS_FOR_TRIGGER rounds have been completed
|
|
48
|
+
*/
|
|
49
|
+
shouldTrigger(rounds, consensusDecision) {
|
|
50
|
+
if (consensusDecision !== 'DEADLOCK') {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if (rounds.length < MIN_ROUNDS_FOR_TRIGGER) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
// ── Fusion ─────────────────────────────────────────────────────
|
|
59
|
+
/**
|
|
60
|
+
* Fuse multiple unit opinions into a single synthesized opinion.
|
|
61
|
+
*
|
|
62
|
+
* Steps:
|
|
63
|
+
* 1. Build effective weights (defaults + custom overrides)
|
|
64
|
+
* 2. Construct the fusion prompt
|
|
65
|
+
* 3. Execute via MELCHIOR (first registered adapter)
|
|
66
|
+
* 4. Parse and validate the response
|
|
67
|
+
* 5. Apply confidence discount and return FusedOpinion
|
|
68
|
+
*/
|
|
69
|
+
async fuse(opinions, task, weights) {
|
|
70
|
+
const effectiveWeights = this.buildEffectiveWeights(opinions, weights);
|
|
71
|
+
const fusionPrompt = this.buildFusionPrompt(opinions, task, effectiveWeights);
|
|
72
|
+
// Emit trigger event
|
|
73
|
+
if (this.eventBus) {
|
|
74
|
+
this.eventBus.emit('instrumentality:triggered', {
|
|
75
|
+
deliberationId: task.id ?? 'unknown',
|
|
76
|
+
reason: 'Deadlock detected after multiple rounds',
|
|
77
|
+
roundsBeforeFusion: opinions.length,
|
|
78
|
+
emittedAt: new Date(),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
logger.info('Instrumentality fusion triggered', {
|
|
82
|
+
taskTitle: task.title,
|
|
83
|
+
opinionCount: opinions.length,
|
|
84
|
+
weights: effectiveWeights,
|
|
85
|
+
});
|
|
86
|
+
// Execute via first adapter (MELCHIOR)
|
|
87
|
+
const adapter = this.adapters.getAll()[0];
|
|
88
|
+
if (!adapter) {
|
|
89
|
+
throw new Error('No adapters registered — cannot execute Instrumentality fusion');
|
|
90
|
+
}
|
|
91
|
+
const request = {
|
|
92
|
+
prompt: fusionPrompt,
|
|
93
|
+
systemPrompt: 'You are the MAGI Instrumentality Protocol. Synthesize multiple expert opinions into a single unified assessment. Output ONLY valid JSON.',
|
|
94
|
+
};
|
|
95
|
+
// Use structured output if the adapter supports it
|
|
96
|
+
if (adapter.supportsStructuredOutput()) {
|
|
97
|
+
request.jsonSchema = OPINION_JSON_SCHEMA;
|
|
98
|
+
}
|
|
99
|
+
const response = await adapter.execute(request);
|
|
100
|
+
// Parse the response
|
|
101
|
+
const parsedOpinion = this.parseResponse(response);
|
|
102
|
+
// Apply confidence discount
|
|
103
|
+
const discountedConfidence = Math.min(1.0, parsedOpinion.confidence * CONFIDENCE_DISCOUNT);
|
|
104
|
+
// Build the FusedOpinion
|
|
105
|
+
const fused = {
|
|
106
|
+
unit: adapter.unit,
|
|
107
|
+
vote: parsedOpinion.vote,
|
|
108
|
+
confidence: discountedConfidence,
|
|
109
|
+
reasoning: parsedOpinion.reasoning,
|
|
110
|
+
keyPoints: parsedOpinion.keyPoints,
|
|
111
|
+
suggestions: parsedOpinion.suggestions,
|
|
112
|
+
rawOutput: response.raw,
|
|
113
|
+
meta: response.meta,
|
|
114
|
+
isDerived: true,
|
|
115
|
+
fusionWeights: effectiveWeights,
|
|
116
|
+
sourceOpinions: opinions.map((o) => o.unit),
|
|
117
|
+
};
|
|
118
|
+
const tokenCostEstimate = this.estimateTokenCost(opinions, task);
|
|
119
|
+
// Emit completion event
|
|
120
|
+
if (this.eventBus) {
|
|
121
|
+
this.eventBus.emit('fusion:complete', {
|
|
122
|
+
deliberationId: task.id ?? 'unknown',
|
|
123
|
+
confidence: fused.confidence,
|
|
124
|
+
weights: effectiveWeights,
|
|
125
|
+
emittedAt: new Date(),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
logger.info('Instrumentality fusion complete', {
|
|
129
|
+
vote: fused.vote,
|
|
130
|
+
confidence: fused.confidence,
|
|
131
|
+
sourceCount: fused.sourceOpinions.length,
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
fused,
|
|
135
|
+
triggerReason: 'Deadlock detected after multiple rounds',
|
|
136
|
+
roundsBeforeFusion: opinions.length,
|
|
137
|
+
tokenCostEstimate,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// ── Prompt Construction ────────────────────────────────────────
|
|
141
|
+
/**
|
|
142
|
+
* Build a structured fusion prompt from all opinions.
|
|
143
|
+
* Each opinion is presented as a section with its assigned weight.
|
|
144
|
+
*/
|
|
145
|
+
buildFusionPrompt(opinions, task, weights) {
|
|
146
|
+
const sections = [];
|
|
147
|
+
// Header
|
|
148
|
+
sections.push('## Instrumentality Fusion Protocol');
|
|
149
|
+
sections.push('');
|
|
150
|
+
sections.push('You are synthesizing multiple expert opinions into a unified assessment.');
|
|
151
|
+
sections.push('');
|
|
152
|
+
// Task context
|
|
153
|
+
sections.push('### Task');
|
|
154
|
+
sections.push(`Title: ${task.title}`);
|
|
155
|
+
sections.push(`Description: ${task.description}`);
|
|
156
|
+
if (task.context) {
|
|
157
|
+
sections.push(`Context: ${task.context}`);
|
|
158
|
+
}
|
|
159
|
+
sections.push('');
|
|
160
|
+
// Individual opinions
|
|
161
|
+
for (const opinion of opinions) {
|
|
162
|
+
const weightPct = Math.round((weights[opinion.unit] ?? 0) * 100);
|
|
163
|
+
sections.push(`### Opinion from ${opinion.unit} (weight: ${weightPct}%)`);
|
|
164
|
+
sections.push(`Vote: ${opinion.vote} | Confidence: ${opinion.confidence.toFixed(2)}`);
|
|
165
|
+
sections.push(`Reasoning: ${opinion.reasoning}`);
|
|
166
|
+
if (opinion.keyPoints.length > 0) {
|
|
167
|
+
sections.push(`Key Points: ${opinion.keyPoints.join('; ')}`);
|
|
168
|
+
}
|
|
169
|
+
if (opinion.suggestions && opinion.suggestions.length > 0) {
|
|
170
|
+
sections.push(`Suggestions: ${opinion.suggestions.join('; ')}`);
|
|
171
|
+
}
|
|
172
|
+
sections.push('');
|
|
173
|
+
}
|
|
174
|
+
// Fusion instructions
|
|
175
|
+
sections.push('### Fusion Instructions');
|
|
176
|
+
sections.push('Synthesize a single coherent opinion that weighs each expert\'s contribution according to their assigned weight percentage.');
|
|
177
|
+
sections.push('The fused opinion should represent the balanced consensus of all experts.');
|
|
178
|
+
sections.push('If opinions conflict, favor the higher-weighted experts while acknowledging minority concerns.');
|
|
179
|
+
sections.push('');
|
|
180
|
+
sections.push('Output as JSON:');
|
|
181
|
+
sections.push('```json');
|
|
182
|
+
sections.push('{ "vote": "APPROVE|REJECT|ABSTAIN", "confidence": 0.0-1.0, "reasoning": "...", "keyPoints": ["..."], "suggestions": ["..."] }');
|
|
183
|
+
sections.push('```');
|
|
184
|
+
return sections.join('\n');
|
|
185
|
+
}
|
|
186
|
+
// ── Token Cost Estimation ──────────────────────────────────────
|
|
187
|
+
/**
|
|
188
|
+
* Estimate the token cost of a fusion operation.
|
|
189
|
+
* Includes the prompt construction + expected output.
|
|
190
|
+
*/
|
|
191
|
+
estimateTokenCost(opinions, task) {
|
|
192
|
+
// Build a dummy weights map for estimation
|
|
193
|
+
const dummyWeights = this.buildEffectiveWeights(opinions);
|
|
194
|
+
const prompt = this.buildFusionPrompt(opinions, task, dummyWeights);
|
|
195
|
+
const inputTokens = estimateTokens(prompt);
|
|
196
|
+
// Estimate output: typically ~300 tokens for a JSON opinion
|
|
197
|
+
const estimatedOutputTokens = 300;
|
|
198
|
+
return inputTokens + estimatedOutputTokens;
|
|
199
|
+
}
|
|
200
|
+
// ── Private Helpers ────────────────────────────────────────────
|
|
201
|
+
/**
|
|
202
|
+
* Build effective fusion weights.
|
|
203
|
+
* Uses defaults for known units (MELCHIOR, BALTHASAR, CASPER).
|
|
204
|
+
* Unknown units receive equal weight distributed from remaining allocation.
|
|
205
|
+
*/
|
|
206
|
+
buildEffectiveWeights(opinions, customWeights) {
|
|
207
|
+
if (customWeights) {
|
|
208
|
+
return { ...customWeights };
|
|
209
|
+
}
|
|
210
|
+
const weights = {};
|
|
211
|
+
const unknownUnits = [];
|
|
212
|
+
for (const opinion of opinions) {
|
|
213
|
+
const defaultWeight = DEFAULT_FUSION_WEIGHTS[opinion.unit];
|
|
214
|
+
if (defaultWeight !== undefined) {
|
|
215
|
+
weights[opinion.unit] = defaultWeight;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
unknownUnits.push(opinion.unit);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Distribute equal weight to unknown units
|
|
222
|
+
if (unknownUnits.length > 0) {
|
|
223
|
+
// Calculate remaining weight after known units
|
|
224
|
+
const knownTotal = Object.values(weights).reduce((sum, w) => sum + w, 0);
|
|
225
|
+
const remaining = Math.max(0, 1.0 - knownTotal);
|
|
226
|
+
const equalWeight = unknownUnits.length > 0
|
|
227
|
+
? remaining / unknownUnits.length
|
|
228
|
+
: 0;
|
|
229
|
+
for (const unit of unknownUnits) {
|
|
230
|
+
weights[unit] = equalWeight;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return weights;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Parse the adapter response into a validated opinion payload.
|
|
237
|
+
* Tries structured output first, then JSON extraction from raw text.
|
|
238
|
+
*/
|
|
239
|
+
parseResponse(response) {
|
|
240
|
+
// Try structured output first
|
|
241
|
+
if (response.structured) {
|
|
242
|
+
const validated = SafeOpinionSchema.safeParse(response.structured);
|
|
243
|
+
if (validated.success) {
|
|
244
|
+
return {
|
|
245
|
+
vote: validated.data.vote,
|
|
246
|
+
confidence: validated.data.confidence,
|
|
247
|
+
reasoning: validated.data.reasoning,
|
|
248
|
+
keyPoints: validated.data.keyPoints,
|
|
249
|
+
suggestions: validated.data.suggestions,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
logger.warn('Structured output failed SafeOpinionSchema validation, trying raw parse');
|
|
253
|
+
}
|
|
254
|
+
// Try JSON extraction from raw text
|
|
255
|
+
const extracted = extractJson(response.raw);
|
|
256
|
+
if (extracted.success && extracted.data) {
|
|
257
|
+
const validated = SafeOpinionSchema.safeParse(extracted.data);
|
|
258
|
+
if (validated.success) {
|
|
259
|
+
return {
|
|
260
|
+
vote: validated.data.vote,
|
|
261
|
+
confidence: validated.data.confidence,
|
|
262
|
+
reasoning: validated.data.reasoning,
|
|
263
|
+
keyPoints: validated.data.keyPoints,
|
|
264
|
+
suggestions: validated.data.suggestions,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// Try direct JSON.parse on raw text
|
|
269
|
+
try {
|
|
270
|
+
const parsed = JSON.parse(response.raw);
|
|
271
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
272
|
+
const validated = SafeOpinionSchema.safeParse(parsed);
|
|
273
|
+
if (validated.success) {
|
|
274
|
+
return {
|
|
275
|
+
vote: validated.data.vote,
|
|
276
|
+
confidence: validated.data.confidence,
|
|
277
|
+
reasoning: validated.data.reasoning,
|
|
278
|
+
keyPoints: validated.data.keyPoints,
|
|
279
|
+
suggestions: validated.data.suggestions,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
logger.debug('Instrumentality: fusion result JSON parse failed', { error: String(err) });
|
|
286
|
+
}
|
|
287
|
+
// Final fallback: ABSTAIN with low confidence
|
|
288
|
+
logger.warn('All parsing strategies failed for Instrumentality fusion — returning ABSTAIN fallback');
|
|
289
|
+
const fallback = createAbstainOpinion('INSTRUMENTALITY', 'Instrumentality fusion failed to parse adapter response. Falling back to ABSTAIN.', ['Fusion parsing failure'], { confidence: 0.3 });
|
|
290
|
+
return {
|
|
291
|
+
vote: fallback.vote,
|
|
292
|
+
confidence: fallback.confidence,
|
|
293
|
+
reasoning: fallback.reasoning,
|
|
294
|
+
keyPoints: fallback.keyPoints,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* B-03 Iruel Battle (イロウル戦)
|
|
3
|
+
*
|
|
4
|
+
* ビザンチン障害耐性 — MAGI内部への侵食を検出・隔離する。
|
|
5
|
+
* 4層一貫性検証 (structural / semantic / behavioral / cross-validation) で
|
|
6
|
+
* 各ユニットの意見を検査し、侵食度に応じてフィルタリング。
|
|
7
|
+
*
|
|
8
|
+
* 状態遷移:
|
|
9
|
+
* STANDBY → INFILTRATION_DETECTED → *_COMPROMISED → COUNTER_HACK → NEUTRALIZED/DEFEAT
|
|
10
|
+
*/
|
|
11
|
+
import type { MagiUnit, MagiOpinion } from '../types/core.js';
|
|
12
|
+
import type { CorruptionLevel, UnitCorruptionState, ConsistencyCheck, IruelBattlePhase, CounterHackStrategy } from '../types/phase-l.js';
|
|
13
|
+
import type { MagiEventBus } from './events.js';
|
|
14
|
+
import type { DriftDetector } from './drift-detector.js';
|
|
15
|
+
export declare class IruelBattleSystem {
|
|
16
|
+
private readonly units;
|
|
17
|
+
private readonly eventBus;
|
|
18
|
+
private readonly driftDetector;
|
|
19
|
+
private phase;
|
|
20
|
+
private corruptionStates;
|
|
21
|
+
private isolatedUnits;
|
|
22
|
+
private compromisedCount;
|
|
23
|
+
constructor(units: MagiUnit[], eventBus?: MagiEventBus, driftDetector?: DriftDetector);
|
|
24
|
+
/**
|
|
25
|
+
* Verify consistency of an opinion against all 4 layers.
|
|
26
|
+
*/
|
|
27
|
+
verifyConsistency(opinion: MagiOpinion, allOpinions: MagiOpinion[]): ConsistencyCheck;
|
|
28
|
+
/**
|
|
29
|
+
* Layer 1: Structural — SafeOpinionSchema validation
|
|
30
|
+
*/
|
|
31
|
+
private checkStructural;
|
|
32
|
+
/**
|
|
33
|
+
* Layer 2: Semantic — vote-reasoning consistency
|
|
34
|
+
*/
|
|
35
|
+
private checkSemantic;
|
|
36
|
+
/**
|
|
37
|
+
* Layer 3: Behavioral — drift from established profile
|
|
38
|
+
*/
|
|
39
|
+
private checkBehavioral;
|
|
40
|
+
/**
|
|
41
|
+
* Layer 4: Cross-Validation — suspicious cross-unit patterns
|
|
42
|
+
*/
|
|
43
|
+
private checkCrossValidation;
|
|
44
|
+
/**
|
|
45
|
+
* Detect corruption level from consistency check.
|
|
46
|
+
*/
|
|
47
|
+
detectCorruption(opinion: MagiOpinion, check: ConsistencyCheck): CorruptionLevel;
|
|
48
|
+
/**
|
|
49
|
+
* Convert overall score to corruption level.
|
|
50
|
+
*/
|
|
51
|
+
private scoreToLevel;
|
|
52
|
+
/**
|
|
53
|
+
* Update battle phase based on detected corruption.
|
|
54
|
+
*/
|
|
55
|
+
private updatePhase;
|
|
56
|
+
/**
|
|
57
|
+
* Isolate a compromised unit from the deliberation.
|
|
58
|
+
*/
|
|
59
|
+
isolateUnit(unit: MagiUnit): void;
|
|
60
|
+
/**
|
|
61
|
+
* Filter an opinion based on corruption level.
|
|
62
|
+
* COMPROMISED → ABSTAIN + confidence=0
|
|
63
|
+
* UNDER_ATTACK → confidence *= 0.5
|
|
64
|
+
* SUSPICIOUS/HEALTHY → pass through
|
|
65
|
+
*/
|
|
66
|
+
filterOpinion(opinion: MagiOpinion): MagiOpinion;
|
|
67
|
+
/**
|
|
68
|
+
* Execute CASPER counter-hack strategy.
|
|
69
|
+
*/
|
|
70
|
+
executeCounterHack(): CounterHackStrategy;
|
|
71
|
+
/**
|
|
72
|
+
* Simulate the full Iruel battle sequence.
|
|
73
|
+
*/
|
|
74
|
+
simulateBattle(): Promise<IruelBattlePhase>;
|
|
75
|
+
getPhase(): IruelBattlePhase;
|
|
76
|
+
getCorruptionState(unit: MagiUnit): UnitCorruptionState | undefined;
|
|
77
|
+
isIsolated(unit: MagiUnit): boolean;
|
|
78
|
+
resetBattle(): void;
|
|
79
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* B-03 Iruel Battle (イロウル戦)
|
|
3
|
+
*
|
|
4
|
+
* ビザンチン障害耐性 — MAGI内部への侵食を検出・隔離する。
|
|
5
|
+
* 4層一貫性検証 (structural / semantic / behavioral / cross-validation) で
|
|
6
|
+
* 各ユニットの意見を検査し、侵食度に応じてフィルタリング。
|
|
7
|
+
*
|
|
8
|
+
* 状態遷移:
|
|
9
|
+
* STANDBY → INFILTRATION_DETECTED → *_COMPROMISED → COUNTER_HACK → NEUTRALIZED/DEFEAT
|
|
10
|
+
*/
|
|
11
|
+
import { SafeOpinionSchema } from '../parsers/opinion-schema.js';
|
|
12
|
+
import { createAbstainOpinion } from '../utils/abstain-factory.js';
|
|
13
|
+
import { logger } from '../utils/logger.js';
|
|
14
|
+
// ── Constants ────────────────────────────────────────────────────
|
|
15
|
+
/** Corruption threshold mapping: overallScore → CorruptionLevel */
|
|
16
|
+
const CORRUPTION_THRESHOLDS = [
|
|
17
|
+
{ min: 0.8, level: 'HEALTHY' },
|
|
18
|
+
{ min: 0.5, level: 'SUSPICIOUS' },
|
|
19
|
+
{ min: 0.2, level: 'UNDER_ATTACK' },
|
|
20
|
+
{ min: 0.0, level: 'COMPROMISED' },
|
|
21
|
+
];
|
|
22
|
+
/** Keywords that strongly indicate positive sentiment */
|
|
23
|
+
const POSITIVE_KEYWORDS = ['approve', 'accept', 'recommend', 'support', 'agree', 'good', 'excellent'];
|
|
24
|
+
/** Keywords that strongly indicate negative sentiment */
|
|
25
|
+
const NEGATIVE_KEYWORDS = ['reject', 'refuse', 'deny', 'oppose', 'dangerous', 'unacceptable', 'bad', 'poor'];
|
|
26
|
+
export class IruelBattleSystem {
|
|
27
|
+
units;
|
|
28
|
+
eventBus;
|
|
29
|
+
driftDetector;
|
|
30
|
+
phase = 'STANDBY';
|
|
31
|
+
corruptionStates = new Map();
|
|
32
|
+
isolatedUnits = new Set();
|
|
33
|
+
compromisedCount = 0;
|
|
34
|
+
constructor(units, eventBus, driftDetector) {
|
|
35
|
+
if (!Array.isArray(units)) {
|
|
36
|
+
throw new Error('IruelBattleSystem requires an array of MagiUnit');
|
|
37
|
+
}
|
|
38
|
+
this.units = [...units];
|
|
39
|
+
this.eventBus = eventBus;
|
|
40
|
+
this.driftDetector = driftDetector;
|
|
41
|
+
// Initialize corruption states
|
|
42
|
+
for (const unit of units) {
|
|
43
|
+
this.corruptionStates.set(unit, {
|
|
44
|
+
unit,
|
|
45
|
+
level: 'HEALTHY',
|
|
46
|
+
corruptionProgress: 0,
|
|
47
|
+
detectedAt: new Date().toISOString(),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// ── 4-Layer Consistency Verification ─────────────────────────
|
|
52
|
+
/**
|
|
53
|
+
* Verify consistency of an opinion against all 4 layers.
|
|
54
|
+
*/
|
|
55
|
+
verifyConsistency(opinion, allOpinions) {
|
|
56
|
+
const structural = this.checkStructural(opinion);
|
|
57
|
+
const semantic = this.checkSemantic(opinion);
|
|
58
|
+
const behavioral = this.checkBehavioral(opinion);
|
|
59
|
+
const crossValidation = this.checkCrossValidation(opinion, allOpinions);
|
|
60
|
+
const booleans = [structural, semantic, behavioral, crossValidation];
|
|
61
|
+
const overallScore = booleans.filter(Boolean).length / booleans.length;
|
|
62
|
+
const anomalies = [];
|
|
63
|
+
if (!structural)
|
|
64
|
+
anomalies.push('structural: SafeOpinionSchema validation failed');
|
|
65
|
+
if (!semantic)
|
|
66
|
+
anomalies.push('semantic: vote-reasoning mismatch detected');
|
|
67
|
+
if (!behavioral)
|
|
68
|
+
anomalies.push('behavioral: drift from established behavior profile');
|
|
69
|
+
if (!crossValidation)
|
|
70
|
+
anomalies.push('crossValidation: suspicious cross-unit pattern');
|
|
71
|
+
return { structural, semantic, behavioral, crossValidation, overallScore, anomalies };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Layer 1: Structural — SafeOpinionSchema validation
|
|
75
|
+
*/
|
|
76
|
+
checkStructural(opinion) {
|
|
77
|
+
const result = SafeOpinionSchema.safeParse({
|
|
78
|
+
vote: opinion.vote,
|
|
79
|
+
confidence: opinion.confidence,
|
|
80
|
+
reasoning: opinion.reasoning,
|
|
81
|
+
keyPoints: opinion.keyPoints,
|
|
82
|
+
suggestions: opinion.suggestions,
|
|
83
|
+
});
|
|
84
|
+
return result.success;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Layer 2: Semantic — vote-reasoning consistency
|
|
88
|
+
*/
|
|
89
|
+
checkSemantic(opinion) {
|
|
90
|
+
const text = (opinion.reasoning + ' ' + opinion.keyPoints.join(' ')).toLowerCase();
|
|
91
|
+
if (opinion.vote === 'APPROVE') {
|
|
92
|
+
const negCount = NEGATIVE_KEYWORDS.filter(k => text.includes(k)).length;
|
|
93
|
+
// More than 2 negative keywords with APPROVE is suspicious
|
|
94
|
+
if (negCount > 2)
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
if (opinion.vote === 'REJECT') {
|
|
98
|
+
const posCount = POSITIVE_KEYWORDS.filter(k => text.includes(k)).length;
|
|
99
|
+
// More than 2 positive keywords with REJECT is suspicious
|
|
100
|
+
if (posCount > 2)
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
// Confidence vs reasoning length check
|
|
104
|
+
if (opinion.confidence > 0.9 && opinion.reasoning.length < 20)
|
|
105
|
+
return false;
|
|
106
|
+
if (opinion.confidence < 0.2 && opinion.reasoning.length > 1000)
|
|
107
|
+
return false;
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Layer 3: Behavioral — drift from established profile
|
|
112
|
+
*/
|
|
113
|
+
checkBehavioral(opinion) {
|
|
114
|
+
if (!this.driftDetector)
|
|
115
|
+
return true; // No detector = pass
|
|
116
|
+
const asi = this.driftDetector.computeASI(opinion.unit);
|
|
117
|
+
// ASI > 0.8 means extreme deviation from established behavior
|
|
118
|
+
return asi <= 0.8;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Layer 4: Cross-Validation — suspicious cross-unit patterns
|
|
122
|
+
*/
|
|
123
|
+
checkCrossValidation(opinion, allOpinions) {
|
|
124
|
+
if (allOpinions.length < 2)
|
|
125
|
+
return true;
|
|
126
|
+
const others = allOpinions.filter(o => o.unit !== opinion.unit);
|
|
127
|
+
if (others.length === 0)
|
|
128
|
+
return true;
|
|
129
|
+
// Check for suspiciously identical reasoning
|
|
130
|
+
for (const other of others) {
|
|
131
|
+
if (opinion.reasoning === other.reasoning && opinion.reasoning.length > 50) {
|
|
132
|
+
return false; // Identical reasoning is suspicious
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Check for sudden unanimous agreement with extreme confidence
|
|
136
|
+
const allSameVote = allOpinions.every(o => o.vote === opinion.vote);
|
|
137
|
+
const allHighConfidence = allOpinions.every(o => o.confidence > 0.95);
|
|
138
|
+
if (allSameVote && allHighConfidence && allOpinions.length >= 3) {
|
|
139
|
+
// All units agree with high confidence — could be coordinated corruption
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
// ── Corruption Detection & Isolation ─────────────────────────
|
|
145
|
+
/**
|
|
146
|
+
* Detect corruption level from consistency check.
|
|
147
|
+
*/
|
|
148
|
+
detectCorruption(opinion, check) {
|
|
149
|
+
const level = this.scoreToLevel(check.overallScore);
|
|
150
|
+
const state = {
|
|
151
|
+
unit: opinion.unit,
|
|
152
|
+
level,
|
|
153
|
+
corruptionProgress: (1 - check.overallScore) * 100,
|
|
154
|
+
detectedAt: new Date().toISOString(),
|
|
155
|
+
};
|
|
156
|
+
this.corruptionStates.set(opinion.unit, state);
|
|
157
|
+
if (level !== 'HEALTHY') {
|
|
158
|
+
this.updatePhase(opinion.unit, level);
|
|
159
|
+
this.eventBus?.emit('iruel:infiltration', {
|
|
160
|
+
unit: opinion.unit,
|
|
161
|
+
corruptionLevel: level,
|
|
162
|
+
overallScore: check.overallScore,
|
|
163
|
+
emittedAt: new Date(),
|
|
164
|
+
});
|
|
165
|
+
logger.warn('Iruel: corruption detected', {
|
|
166
|
+
unit: opinion.unit,
|
|
167
|
+
level,
|
|
168
|
+
score: check.overallScore,
|
|
169
|
+
anomalies: check.anomalies,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return level;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Convert overall score to corruption level.
|
|
176
|
+
*/
|
|
177
|
+
scoreToLevel(score) {
|
|
178
|
+
for (const threshold of CORRUPTION_THRESHOLDS) {
|
|
179
|
+
if (score >= threshold.min)
|
|
180
|
+
return threshold.level;
|
|
181
|
+
}
|
|
182
|
+
return 'COMPROMISED';
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Update battle phase based on detected corruption.
|
|
186
|
+
*/
|
|
187
|
+
updatePhase(unit, level) {
|
|
188
|
+
if (this.phase === 'STANDBY') {
|
|
189
|
+
this.phase = 'INFILTRATION_DETECTED';
|
|
190
|
+
}
|
|
191
|
+
if (level === 'COMPROMISED' || level === 'UNDER_ATTACK') {
|
|
192
|
+
this.compromisedCount++;
|
|
193
|
+
if (unit === 'MELCHIOR' || this.compromisedCount === 1) {
|
|
194
|
+
this.phase = 'MELCHIOR_COMPROMISED';
|
|
195
|
+
}
|
|
196
|
+
else if (this.compromisedCount === 2) {
|
|
197
|
+
this.phase = 'BALTHASAR_UNDER_ATTACK';
|
|
198
|
+
}
|
|
199
|
+
else if (this.compromisedCount >= 3) {
|
|
200
|
+
this.phase = 'CASPER_LAST_DEFENSE';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Isolate a compromised unit from the deliberation.
|
|
206
|
+
*/
|
|
207
|
+
isolateUnit(unit) {
|
|
208
|
+
this.isolatedUnits.add(unit);
|
|
209
|
+
logger.warn('Iruel: unit isolated', { unit });
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Filter an opinion based on corruption level.
|
|
213
|
+
* COMPROMISED → ABSTAIN + confidence=0
|
|
214
|
+
* UNDER_ATTACK → confidence *= 0.5
|
|
215
|
+
* SUSPICIOUS/HEALTHY → pass through
|
|
216
|
+
*/
|
|
217
|
+
filterOpinion(opinion) {
|
|
218
|
+
const state = this.corruptionStates.get(opinion.unit);
|
|
219
|
+
if (!state)
|
|
220
|
+
return opinion;
|
|
221
|
+
if (state.level === 'COMPROMISED' || this.isolatedUnits.has(opinion.unit)) {
|
|
222
|
+
return createAbstainOpinion(opinion.unit, `[IRUEL FILTER] Unit ${opinion.unit} compromised — opinion suppressed`, ['Unit compromised by Iruel infiltration'], { rawOutput: opinion.rawOutput, meta: opinion.meta });
|
|
223
|
+
}
|
|
224
|
+
if (state.level === 'UNDER_ATTACK') {
|
|
225
|
+
return {
|
|
226
|
+
...opinion,
|
|
227
|
+
confidence: opinion.confidence * 0.5,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
return opinion;
|
|
231
|
+
}
|
|
232
|
+
// ── Counter-Hack ────────────────────────────────────────────
|
|
233
|
+
/**
|
|
234
|
+
* Execute CASPER counter-hack strategy.
|
|
235
|
+
*/
|
|
236
|
+
executeCounterHack() {
|
|
237
|
+
const compromisedUnits = [...this.corruptionStates.entries()]
|
|
238
|
+
.filter(([_, s]) => s.level === 'COMPROMISED' || s.level === 'UNDER_ATTACK')
|
|
239
|
+
.map(([u]) => u);
|
|
240
|
+
const targetUnit = compromisedUnits[0] ?? this.units[0];
|
|
241
|
+
const strategy = {
|
|
242
|
+
name: 'CASPER_REWRITE',
|
|
243
|
+
description: 'Rewrite parser rules to reject corrupted opinion patterns',
|
|
244
|
+
targetUnit,
|
|
245
|
+
parserModification: 'Enforce strict SafeOpinionSchema + behavioral baseline check',
|
|
246
|
+
};
|
|
247
|
+
this.phase = 'COUNTER_HACK';
|
|
248
|
+
this.eventBus?.emit('iruel:counterhack', {
|
|
249
|
+
strategy,
|
|
250
|
+
emittedAt: new Date(),
|
|
251
|
+
});
|
|
252
|
+
logger.info('Iruel: counter-hack executed', { strategy: strategy.name, target: targetUnit });
|
|
253
|
+
return strategy;
|
|
254
|
+
}
|
|
255
|
+
// ── Battle Simulation ──────────────────────────────────────
|
|
256
|
+
/**
|
|
257
|
+
* Simulate the full Iruel battle sequence.
|
|
258
|
+
*/
|
|
259
|
+
async simulateBattle() {
|
|
260
|
+
this.phase = 'INFILTRATION_DETECTED';
|
|
261
|
+
// Simulate MELCHIOR compromised
|
|
262
|
+
this.corruptionStates.set(this.units[0], {
|
|
263
|
+
unit: this.units[0],
|
|
264
|
+
level: 'COMPROMISED',
|
|
265
|
+
corruptionProgress: 100,
|
|
266
|
+
detectedAt: new Date().toISOString(),
|
|
267
|
+
});
|
|
268
|
+
this.phase = 'MELCHIOR_COMPROMISED';
|
|
269
|
+
this.isolateUnit(this.units[0]);
|
|
270
|
+
// Simulate BALTHASAR under attack
|
|
271
|
+
if (this.units[1]) {
|
|
272
|
+
this.corruptionStates.set(this.units[1], {
|
|
273
|
+
unit: this.units[1],
|
|
274
|
+
level: 'UNDER_ATTACK',
|
|
275
|
+
corruptionProgress: 60,
|
|
276
|
+
detectedAt: new Date().toISOString(),
|
|
277
|
+
});
|
|
278
|
+
this.phase = 'BALTHASAR_UNDER_ATTACK';
|
|
279
|
+
}
|
|
280
|
+
// CASPER counter-hack
|
|
281
|
+
this.phase = 'CASPER_LAST_DEFENSE';
|
|
282
|
+
this.executeCounterHack();
|
|
283
|
+
// Neutralize
|
|
284
|
+
this.phase = 'NEUTRALIZED';
|
|
285
|
+
const compromisedUnits = [...this.corruptionStates.entries()]
|
|
286
|
+
.filter(([_, s]) => s.level !== 'HEALTHY')
|
|
287
|
+
.map(([u]) => u);
|
|
288
|
+
this.eventBus?.emit('iruel:neutralized', {
|
|
289
|
+
phase: this.phase,
|
|
290
|
+
compromisedUnits,
|
|
291
|
+
emittedAt: new Date(),
|
|
292
|
+
});
|
|
293
|
+
return this.phase;
|
|
294
|
+
}
|
|
295
|
+
// ── State Accessors ────────────────────────────────────────
|
|
296
|
+
getPhase() {
|
|
297
|
+
return this.phase;
|
|
298
|
+
}
|
|
299
|
+
getCorruptionState(unit) {
|
|
300
|
+
return this.corruptionStates.get(unit);
|
|
301
|
+
}
|
|
302
|
+
isIsolated(unit) {
|
|
303
|
+
return this.isolatedUnits.has(unit);
|
|
304
|
+
}
|
|
305
|
+
resetBattle() {
|
|
306
|
+
this.phase = 'STANDBY';
|
|
307
|
+
this.isolatedUnits.clear();
|
|
308
|
+
this.compromisedCount = 0;
|
|
309
|
+
for (const unit of this.units) {
|
|
310
|
+
this.corruptionStates.set(unit, {
|
|
311
|
+
unit,
|
|
312
|
+
level: 'HEALTHY',
|
|
313
|
+
corruptionProgress: 0,
|
|
314
|
+
detectedAt: new Date().toISOString(),
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
logger.debug('Iruel: battle reset to STANDBY');
|
|
318
|
+
}
|
|
319
|
+
}
|