scene-capability-engine 3.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/CHANGELOG.md +2513 -0
- package/LICENSE +21 -0
- package/README.md +765 -0
- package/README.zh.md +630 -0
- package/bin/kiro-spec-engine.js +796 -0
- package/bin/kse.js +3 -0
- package/bin/sce.js +3 -0
- package/bin/sco.js +3 -0
- package/docs/331-poc-adaptation-roadmap.md +156 -0
- package/docs/331-poc-dual-track-integration-guide.md +120 -0
- package/docs/331-poc-weekly-delivery-checklist.md +52 -0
- package/docs/OFFLINE_INSTALL.md +96 -0
- package/docs/README.md +279 -0
- package/docs/adopt-migration-guide.md +599 -0
- package/docs/adoption-guide.md +616 -0
- package/docs/agent-hooks-analysis.md +815 -0
- package/docs/architecture.md +733 -0
- package/docs/articles/ai-driven-development-philosophy-and-practice-review.md +208 -0
- package/docs/articles/ai-driven-development-philosophy-and-practice.en.md +459 -0
- package/docs/articles/ai-driven-development-philosophy-and-practice.md +492 -0
- package/docs/autonomous-control-guide.md +851 -0
- package/docs/command-reference.md +1368 -0
- package/docs/community.md +115 -0
- package/docs/cross-tool-guide.md +555 -0
- package/docs/developer-guide.md +619 -0
- package/docs/document-governance.md +865 -0
- package/docs/environment-management-guide.md +526 -0
- package/docs/examples/add-export-command/design.md +194 -0
- package/docs/examples/add-export-command/requirements.md +110 -0
- package/docs/examples/add-export-command/tasks.md +88 -0
- package/docs/examples/add-rest-api/design.md +855 -0
- package/docs/examples/add-rest-api/requirements.md +323 -0
- package/docs/examples/add-rest-api/tasks.md +355 -0
- package/docs/examples/add-user-dashboard/design.md +192 -0
- package/docs/examples/add-user-dashboard/requirements.md +143 -0
- package/docs/examples/add-user-dashboard/tasks.md +91 -0
- package/docs/faq.md +697 -0
- package/docs/handoffs/evidence/ontology/moqui-template-baseline-2026-02-17-232922.json +156 -0
- package/docs/handoffs/evidence/ontology/moqui-template-baseline-2026-02-17-232922.md +24 -0
- package/docs/images/wechat-qr.png +0 -0
- package/docs/integration-modes.md +529 -0
- package/docs/integration-philosophy.md +313 -0
- package/docs/knowledge-management-guide.md +263 -0
- package/docs/manual-workflows-guide.md +418 -0
- package/docs/moqui-capability-matrix.md +73 -0
- package/docs/moqui-template-core-library-playbook.md +109 -0
- package/docs/multi-agent-coordination-guide.md +553 -0
- package/docs/multi-repo-management-guide.md +1344 -0
- package/docs/quick-start-with-ai-tools.md +375 -0
- package/docs/quick-start.md +146 -0
- package/docs/release-checklist.md +121 -0
- package/docs/releases/README.md +13 -0
- package/docs/releases/v1.46.2-validation.md +45 -0
- package/docs/releases/v1.46.2.md +50 -0
- package/docs/scene-runtime-guide.md +347 -0
- package/docs/spec-collaboration-guide.md +369 -0
- package/docs/spec-locking-guide.md +225 -0
- package/docs/spec-numbering-guide.md +348 -0
- package/docs/spec-workflow.md +519 -0
- package/docs/steering-strategy-guide.md +196 -0
- package/docs/team-collaboration-guide.md +465 -0
- package/docs/testing-strategy.md +272 -0
- package/docs/tools/claude-guide.md +654 -0
- package/docs/tools/cursor-guide.md +706 -0
- package/docs/tools/generic-guide.md +446 -0
- package/docs/tools/kiro-guide.md +308 -0
- package/docs/tools/vscode-guide.md +445 -0
- package/docs/tools/windsurf-guide.md +391 -0
- package/docs/troubleshooting.md +1135 -0
- package/docs/upgrade-guide.md +639 -0
- package/docs/value-observability-guide.md +127 -0
- package/docs/zh/README.md +341 -0
- package/docs/zh/quick-start.md +764 -0
- package/docs/zh/release-checklist.md +121 -0
- package/docs/zh/releases/README.md +13 -0
- package/docs/zh/releases/v1.46.2-validation.md +45 -0
- package/docs/zh/releases/v1.46.2.md +50 -0
- package/docs/zh/spec-numbering-guide.md +348 -0
- package/docs/zh/tools/claude-guide.md +349 -0
- package/docs/zh/tools/cursor-guide.md +281 -0
- package/docs/zh/tools/generic-guide.md +499 -0
- package/docs/zh/tools/kiro-guide.md +342 -0
- package/docs/zh/tools/vscode-guide.md +449 -0
- package/docs/zh/tools/windsurf-guide.md +378 -0
- package/docs/zh/value-observability-guide.md +127 -0
- package/docs//344/272/244/344/273/230/346/270/205/345/215/225.md +75 -0
- package/lib/adoption/adoption-logger.js +487 -0
- package/lib/adoption/adoption-strategy.js +538 -0
- package/lib/adoption/backup-manager.js +420 -0
- package/lib/adoption/conflict-resolver.js +410 -0
- package/lib/adoption/detection-engine.js +275 -0
- package/lib/adoption/diff-viewer.js +226 -0
- package/lib/adoption/error-formatter.js +509 -0
- package/lib/adoption/file-classifier.js +385 -0
- package/lib/adoption/progress-reporter.js +534 -0
- package/lib/adoption/smart-orchestrator.js +470 -0
- package/lib/adoption/strategy-selector.js +218 -0
- package/lib/adoption/summary-generator.js +493 -0
- package/lib/adoption/template-sync.js +605 -0
- package/lib/auto/autonomous-engine.js +485 -0
- package/lib/auto/checkpoint-manager.js +300 -0
- package/lib/auto/close-loop-runner.js +2476 -0
- package/lib/auto/config-schema.js +176 -0
- package/lib/auto/decision-engine.js +344 -0
- package/lib/auto/error-recovery-manager.js +580 -0
- package/lib/auto/goal-decomposer.js +278 -0
- package/lib/auto/progress-tracker.js +502 -0
- package/lib/auto/safety-manager.js +186 -0
- package/lib/auto/semantic-decomposer.js +137 -0
- package/lib/auto/state-manager.js +126 -0
- package/lib/auto/task-queue-manager.js +340 -0
- package/lib/backup/backup-system.js +372 -0
- package/lib/backup/selective-backup.js +207 -0
- package/lib/collab/agent-registry.js +240 -0
- package/lib/collab/collab-manager.js +285 -0
- package/lib/collab/contract-manager.js +320 -0
- package/lib/collab/coordinator.js +370 -0
- package/lib/collab/dependency-manager.js +280 -0
- package/lib/collab/index.js +20 -0
- package/lib/collab/integration-manager.js +202 -0
- package/lib/collab/merge-coordinator.js +252 -0
- package/lib/collab/metadata-manager.js +233 -0
- package/lib/collab/multi-agent-config.js +120 -0
- package/lib/collab/spec-lifecycle-manager.js +304 -0
- package/lib/collab/sync-barrier.js +88 -0
- package/lib/collab/visualizer.js +208 -0
- package/lib/commands/adopt.js +749 -0
- package/lib/commands/auto.js +19559 -0
- package/lib/commands/collab.js +275 -0
- package/lib/commands/context.js +99 -0
- package/lib/commands/docs.js +808 -0
- package/lib/commands/doctor.js +273 -0
- package/lib/commands/env.js +420 -0
- package/lib/commands/knowledge.js +309 -0
- package/lib/commands/lock.js +235 -0
- package/lib/commands/ops.js +409 -0
- package/lib/commands/orchestrate.js +446 -0
- package/lib/commands/prompt.js +105 -0
- package/lib/commands/repo.js +118 -0
- package/lib/commands/rollback.js +219 -0
- package/lib/commands/scene.js +15549 -0
- package/lib/commands/spec-bootstrap.js +147 -0
- package/lib/commands/spec-gate.js +157 -0
- package/lib/commands/spec-pipeline.js +205 -0
- package/lib/commands/status.js +321 -0
- package/lib/commands/task.js +199 -0
- package/lib/commands/templates.js +654 -0
- package/lib/commands/upgrade.js +231 -0
- package/lib/commands/value.js +569 -0
- package/lib/commands/watch.js +684 -0
- package/lib/commands/workflows.js +240 -0
- package/lib/commands/workspace-multi.js +325 -0
- package/lib/commands/workspace.js +189 -0
- package/lib/context/context-exporter.js +378 -0
- package/lib/context/prompt-generator.js +482 -0
- package/lib/data/moqui-capability-lexicon.json +45 -0
- package/lib/environment/backup-system.js +189 -0
- package/lib/environment/environment-manager.js +379 -0
- package/lib/environment/environment-registry.js +168 -0
- package/lib/gitignore/gitignore-backup.js +229 -0
- package/lib/gitignore/gitignore-detector.js +239 -0
- package/lib/gitignore/gitignore-integration.js +267 -0
- package/lib/gitignore/gitignore-transformer.js +193 -0
- package/lib/gitignore/layered-rules-template.js +42 -0
- package/lib/governance/archive-tool.js +284 -0
- package/lib/governance/cleanup-tool.js +237 -0
- package/lib/governance/config-manager.js +186 -0
- package/lib/governance/diagnostic-engine.js +271 -0
- package/lib/governance/doc-reference-checker.js +200 -0
- package/lib/governance/execution-logger.js +243 -0
- package/lib/governance/file-scanner.js +285 -0
- package/lib/governance/hooks-manager.js +333 -0
- package/lib/governance/reporter.js +337 -0
- package/lib/governance/validation-engine.js +181 -0
- package/lib/i18n.js +79 -0
- package/lib/knowledge/entry-manager.js +208 -0
- package/lib/knowledge/index-manager.js +261 -0
- package/lib/knowledge/knowledge-manager.js +273 -0
- package/lib/knowledge/template-manager.js +191 -0
- package/lib/lock/index.js +21 -0
- package/lib/lock/lock-file.js +192 -0
- package/lib/lock/lock-manager.js +321 -0
- package/lib/lock/machine-identifier.js +135 -0
- package/lib/lock/steering-file-lock.js +207 -0
- package/lib/lock/task-lock-manager.js +345 -0
- package/lib/operations/audit-logger.js +293 -0
- package/lib/operations/feedback-manager.js +1147 -0
- package/lib/operations/index.js +23 -0
- package/lib/operations/models/index.js +170 -0
- package/lib/operations/operations-manager.js +151 -0
- package/lib/operations/operations-validator.js +280 -0
- package/lib/operations/permission-manager.js +354 -0
- package/lib/operations/template-loader.js +143 -0
- package/lib/orchestrator/agent-spawner.js +629 -0
- package/lib/orchestrator/bootstrap-prompt-builder.js +236 -0
- package/lib/orchestrator/index.js +19 -0
- package/lib/orchestrator/orchestration-engine.js +1270 -0
- package/lib/orchestrator/orchestrator-config.js +173 -0
- package/lib/orchestrator/status-monitor.js +591 -0
- package/lib/python-checker.js +209 -0
- package/lib/repo/config-manager.js +580 -0
- package/lib/repo/errors/config-error.js +13 -0
- package/lib/repo/errors/git-error.js +15 -0
- package/lib/repo/errors/repo-error.js +14 -0
- package/lib/repo/git-operations.js +181 -0
- package/lib/repo/handlers/.gitkeep +1 -0
- package/lib/repo/handlers/exec-handler.js +155 -0
- package/lib/repo/handlers/health-handler.js +169 -0
- package/lib/repo/handlers/init-handler.js +197 -0
- package/lib/repo/handlers/status-handler.js +176 -0
- package/lib/repo/output-formatter.js +184 -0
- package/lib/repo/path-resolver.js +178 -0
- package/lib/repo/repo-manager.js +514 -0
- package/lib/scene-runtime/audit-emitter.js +59 -0
- package/lib/scene-runtime/binding-plugin-loader.js +351 -0
- package/lib/scene-runtime/binding-registry.js +349 -0
- package/lib/scene-runtime/eval-bridge.js +44 -0
- package/lib/scene-runtime/index.js +19 -0
- package/lib/scene-runtime/moqui-adapter.js +620 -0
- package/lib/scene-runtime/moqui-client.js +606 -0
- package/lib/scene-runtime/moqui-extractor.js +2029 -0
- package/lib/scene-runtime/plan-compiler.js +208 -0
- package/lib/scene-runtime/policy-gate.js +58 -0
- package/lib/scene-runtime/runtime-executor.js +358 -0
- package/lib/scene-runtime/scene-loader.js +96 -0
- package/lib/scene-runtime/scene-ontology.js +959 -0
- package/lib/scene-runtime/scene-template-linter.js +852 -0
- package/lib/scene-runtime/templates/scene-template-erp-query-v0.1.yaml +28 -0
- package/lib/scene-runtime/templates/scene-template-hybrid-shadow-v0.1.yaml +34 -0
- package/lib/spec/bootstrap/context-collector.js +48 -0
- package/lib/spec/bootstrap/draft-generator.js +158 -0
- package/lib/spec/bootstrap/questionnaire-engine.js +70 -0
- package/lib/spec/bootstrap/trace-emitter.js +59 -0
- package/lib/spec/multi-spec-orchestrate.js +93 -0
- package/lib/spec/pipeline/constants.js +6 -0
- package/lib/spec/pipeline/stage-adapters.js +118 -0
- package/lib/spec/pipeline/stage-runner.js +146 -0
- package/lib/spec/pipeline/state-store.js +119 -0
- package/lib/spec-gate/engine/gate-engine.js +165 -0
- package/lib/spec-gate/policy/default-policy.js +22 -0
- package/lib/spec-gate/policy/policy-loader.js +103 -0
- package/lib/spec-gate/result-emitter.js +81 -0
- package/lib/spec-gate/rules/default-rules.js +156 -0
- package/lib/spec-gate/rules/rule-registry.js +51 -0
- package/lib/steering/adoption-config.js +164 -0
- package/lib/steering/compliance-auto-fixer.js +204 -0
- package/lib/steering/compliance-cache.js +99 -0
- package/lib/steering/compliance-error-reporter.js +70 -0
- package/lib/steering/context-sync-manager.js +273 -0
- package/lib/steering/index.js +92 -0
- package/lib/steering/spec-steering.js +230 -0
- package/lib/steering/steering-compliance-checker.js +73 -0
- package/lib/steering/steering-loader.js +144 -0
- package/lib/steering/steering-manager.js +289 -0
- package/lib/task/index.js +12 -0
- package/lib/task/task-claimer.js +489 -0
- package/lib/task/task-status-store.js +418 -0
- package/lib/templates/cache-manager.js +440 -0
- package/lib/templates/content-generalizer.js +247 -0
- package/lib/templates/frontmatter-generator.js +128 -0
- package/lib/templates/git-handler.js +471 -0
- package/lib/templates/metadata-collector.js +328 -0
- package/lib/templates/path-utils.js +144 -0
- package/lib/templates/registry-parser.js +505 -0
- package/lib/templates/spec-reader.js +216 -0
- package/lib/templates/template-applicator.js +249 -0
- package/lib/templates/template-creator.js +256 -0
- package/lib/templates/template-error.js +143 -0
- package/lib/templates/template-exporter.js +502 -0
- package/lib/templates/template-manager.js +782 -0
- package/lib/templates/template-validator.js +361 -0
- package/lib/upgrade/migration-engine.js +382 -0
- package/lib/upgrade/migrations/.gitkeep +52 -0
- package/lib/upgrade/migrations/1.0.0-to-1.1.0.js +78 -0
- package/lib/utils/file-diff.js +177 -0
- package/lib/utils/fs-utils.js +274 -0
- package/lib/utils/tool-detector.js +383 -0
- package/lib/utils/validation.js +324 -0
- package/lib/value/gate-summary-emitter.js +99 -0
- package/lib/value/metric-contract-loader.js +210 -0
- package/lib/value/risk-evaluator.js +117 -0
- package/lib/value/weekly-snapshot-builder.js +61 -0
- package/lib/version/version-checker.js +156 -0
- package/lib/version/version-manager.js +327 -0
- package/lib/watch/action-executor.js +458 -0
- package/lib/watch/event-debouncer.js +323 -0
- package/lib/watch/execution-logger.js +550 -0
- package/lib/watch/file-watcher.js +499 -0
- package/lib/watch/presets.js +266 -0
- package/lib/watch/watch-manager.js +533 -0
- package/lib/workspace/multi/global-config.js +150 -0
- package/lib/workspace/multi/index.js +22 -0
- package/lib/workspace/multi/path-utils.js +173 -0
- package/lib/workspace/multi/workspace-context-resolver.js +244 -0
- package/lib/workspace/multi/workspace-registry.js +196 -0
- package/lib/workspace/multi/workspace-state-manager.js +537 -0
- package/lib/workspace/multi/workspace.js +90 -0
- package/lib/workspace/workspace-manager.js +370 -0
- package/lib/workspace/workspace-sync.js +356 -0
- package/locales/en.json +114 -0
- package/locales/zh.json +114 -0
- package/package.json +102 -0
- package/template/.kiro/README.md +247 -0
- package/template/.kiro/hooks/check-spec-on-create.kiro.hook +17 -0
- package/template/.kiro/hooks/run-tests-on-save.kiro.hook +13 -0
- package/template/.kiro/hooks/sync-tasks-on-edit.kiro.hook +16 -0
- package/template/.kiro/specs/SPEC_WORKFLOW_GUIDE.md +134 -0
- package/template/.kiro/steering/CORE_PRINCIPLES.md +133 -0
- package/template/.kiro/steering/CURRENT_CONTEXT.md +30 -0
- package/template/.kiro/steering/ENVIRONMENT.md +35 -0
- package/template/.kiro/steering/RULES_GUIDE.md +46 -0
- package/template/.kiro/templates/operations/default/change-impact.md +112 -0
- package/template/.kiro/templates/operations/default/deployment.md +91 -0
- package/template/.kiro/templates/operations/default/feedback-response.md +269 -0
- package/template/.kiro/templates/operations/default/migration-plan.md +172 -0
- package/template/.kiro/templates/operations/default/monitoring.md +135 -0
- package/template/.kiro/templates/operations/default/operations.md +135 -0
- package/template/.kiro/templates/operations/default/rollback.md +143 -0
- package/template/.kiro/templates/operations/default/tools.yaml +364 -0
- package/template/.kiro/templates/operations/default/troubleshooting.md +123 -0
- package/template/.kiro/tools/backup_manager.py +295 -0
- package/template/.kiro/tools/configuration_manager.py +218 -0
- package/template/.kiro/tools/document_evaluator.py +550 -0
- package/template/.kiro/tools/enhancement_logger.py +168 -0
- package/template/.kiro/tools/error_handler.py +335 -0
- package/template/.kiro/tools/improvement_identifier.py +444 -0
- package/template/.kiro/tools/modification_applicator.py +737 -0
- package/template/.kiro/tools/quality_gate_enforcer.py +207 -0
- package/template/.kiro/tools/quality_scorer.py +305 -0
- package/template/.kiro/tools/report_generator.py +154 -0
- package/template/.kiro/tools/ultrawork_enhancer.py +676 -0
- package/template/.kiro/tools/ultrawork_enhancer_refactored.py +0 -0
- package/template/.kiro/tools/ultrawork_enhancer_v2.py +463 -0
- package/template/.kiro/tools/ultrawork_enhancer_v3.py +606 -0
- package/template/.kiro/tools/workflow_quality_gate.py +100 -0
- package/template/README.md +111 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TaskLockManager - Task-level fine-grained lock manager
|
|
3
|
+
*
|
|
4
|
+
* Provides per-task locking instead of per-Spec locking, allowing multiple
|
|
5
|
+
* Agents to work on different tasks within the same Spec concurrently.
|
|
6
|
+
*
|
|
7
|
+
* Lock file path: `.kiro/specs/{specName}/locks/{taskId}.lock`
|
|
8
|
+
* (dots in taskId replaced with dashes for filesystem safety)
|
|
9
|
+
*
|
|
10
|
+
* In single-Agent mode, delegates to the existing LockManager (Spec-level lock).
|
|
11
|
+
*
|
|
12
|
+
* Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs').promises;
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const fsUtils = require('../utils/fs-utils');
|
|
18
|
+
const { MultiAgentConfig } = require('../collab/multi-agent-config');
|
|
19
|
+
const { LockManager } = require('./lock-manager');
|
|
20
|
+
const TaskClaimer = require('../task/task-claimer');
|
|
21
|
+
|
|
22
|
+
class TaskLockManager {
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} workspaceRoot - Absolute path to the project root
|
|
25
|
+
* @param {import('./machine-identifier').MachineIdentifier} machineIdentifier
|
|
26
|
+
*/
|
|
27
|
+
constructor(workspaceRoot, machineIdentifier) {
|
|
28
|
+
this._workspaceRoot = workspaceRoot;
|
|
29
|
+
this._machineIdentifier = machineIdentifier;
|
|
30
|
+
this._specsDir = path.join(workspaceRoot, '.kiro', 'specs');
|
|
31
|
+
this._multiAgentConfig = new MultiAgentConfig(workspaceRoot);
|
|
32
|
+
this._lockManager = new LockManager(workspaceRoot, machineIdentifier);
|
|
33
|
+
this._taskClaimer = new TaskClaimer();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Convert a taskId to a filesystem-safe filename.
|
|
38
|
+
* Replaces dots with dashes (e.g. "2.1" → "2-1").
|
|
39
|
+
* @param {string} taskId
|
|
40
|
+
* @returns {string}
|
|
41
|
+
* @private
|
|
42
|
+
*/
|
|
43
|
+
_safeTaskId(taskId) {
|
|
44
|
+
return taskId.replace(/\./g, '-');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get the locks directory for a spec.
|
|
49
|
+
* @param {string} specName
|
|
50
|
+
* @returns {string}
|
|
51
|
+
* @private
|
|
52
|
+
*/
|
|
53
|
+
_locksDir(specName) {
|
|
54
|
+
return path.join(this._specsDir, specName, 'locks');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get the lock file path for a specific task.
|
|
59
|
+
* @param {string} specName
|
|
60
|
+
* @param {string} taskId
|
|
61
|
+
* @returns {string}
|
|
62
|
+
* @private
|
|
63
|
+
*/
|
|
64
|
+
_lockFilePath(specName, taskId) {
|
|
65
|
+
return path.join(this._locksDir(specName), `${this._safeTaskId(taskId)}.lock`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check whether multi-Agent mode is enabled.
|
|
70
|
+
* @returns {Promise<boolean>}
|
|
71
|
+
*/
|
|
72
|
+
async isMultiAgentMode() {
|
|
73
|
+
return this._multiAgentConfig.isEnabled();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Acquire a task-level lock.
|
|
78
|
+
*
|
|
79
|
+
* In multi-Agent mode: creates `.kiro/specs/{specName}/locks/{taskId}.lock`
|
|
80
|
+
* In single-Agent mode: delegates to LockManager.acquireLock(specName)
|
|
81
|
+
*
|
|
82
|
+
* @param {string} specName
|
|
83
|
+
* @param {string} taskId
|
|
84
|
+
* @param {string} agentId
|
|
85
|
+
* @param {object} [options={}]
|
|
86
|
+
* @param {string} [options.reason] - Reason for acquiring the lock
|
|
87
|
+
* @returns {Promise<{success: boolean, lock?: object, holder?: object, error?: string}>}
|
|
88
|
+
*/
|
|
89
|
+
async acquireTaskLock(specName, taskId, agentId, options = {}) {
|
|
90
|
+
const multiAgent = await this.isMultiAgentMode();
|
|
91
|
+
|
|
92
|
+
if (!multiAgent) {
|
|
93
|
+
// Single-Agent mode: delegate to Spec-level lock
|
|
94
|
+
return this._lockManager.acquireLock(specName, { reason: options.reason });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const lockPath = this._lockFilePath(specName, taskId);
|
|
98
|
+
|
|
99
|
+
// Check for existing lock
|
|
100
|
+
const existing = await this._readLockFile(lockPath);
|
|
101
|
+
if (existing) {
|
|
102
|
+
if (existing.agentId === agentId) {
|
|
103
|
+
// Already held by this agent – refresh
|
|
104
|
+
const lock = await this._buildLockRecord(specName, taskId, agentId, options.reason);
|
|
105
|
+
await this._atomicWriteLock(lockPath, lock);
|
|
106
|
+
return { success: true, lock };
|
|
107
|
+
}
|
|
108
|
+
// Held by another agent (Req 2.2)
|
|
109
|
+
return { success: false, error: 'Task is already locked', holder: existing };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Acquire new lock (Req 2.1)
|
|
113
|
+
const lock = await this._buildLockRecord(specName, taskId, agentId, options.reason);
|
|
114
|
+
await fsUtils.ensureDirectory(this._locksDir(specName));
|
|
115
|
+
await this._atomicWriteLock(lockPath, lock);
|
|
116
|
+
|
|
117
|
+
// Verify we actually got the lock (race-condition guard)
|
|
118
|
+
const verify = await this._readLockFile(lockPath);
|
|
119
|
+
if (verify && verify.agentId === agentId) {
|
|
120
|
+
// Integrate with TaskClaimer: claim the task atomically (Req 2.5)
|
|
121
|
+
const claimResult = await this._taskClaimer.claimTask(
|
|
122
|
+
this._workspaceRoot, specName, taskId, agentId, true
|
|
123
|
+
);
|
|
124
|
+
if (!claimResult.success) {
|
|
125
|
+
// Rollback: release the lock file since claim failed
|
|
126
|
+
try {
|
|
127
|
+
await fs.unlink(lockPath);
|
|
128
|
+
} catch (unlinkErr) {
|
|
129
|
+
if (unlinkErr.code !== 'ENOENT') throw unlinkErr;
|
|
130
|
+
}
|
|
131
|
+
return { success: false, error: `Lock acquired but claim failed: ${claimResult.error}` };
|
|
132
|
+
}
|
|
133
|
+
return { success: true, lock };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { success: false, error: 'Task is already locked', holder: verify };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Release a task-level lock.
|
|
141
|
+
*
|
|
142
|
+
* In multi-Agent mode: deletes the lock file (Req 2.3)
|
|
143
|
+
* In single-Agent mode: delegates to LockManager.releaseLock(specName)
|
|
144
|
+
*
|
|
145
|
+
* @param {string} specName
|
|
146
|
+
* @param {string} taskId
|
|
147
|
+
* @param {string} agentId
|
|
148
|
+
* @returns {Promise<{success: boolean, error?: string}>}
|
|
149
|
+
*/
|
|
150
|
+
async releaseTaskLock(specName, taskId, agentId) {
|
|
151
|
+
const multiAgent = await this.isMultiAgentMode();
|
|
152
|
+
|
|
153
|
+
if (!multiAgent) {
|
|
154
|
+
return this._lockManager.releaseLock(specName);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const lockPath = this._lockFilePath(specName, taskId);
|
|
158
|
+
const existing = await this._readLockFile(lockPath);
|
|
159
|
+
|
|
160
|
+
if (!existing) {
|
|
161
|
+
return { success: true }; // Nothing to release
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (existing.agentId !== agentId) {
|
|
165
|
+
return { success: false, error: 'Lock owned by different agent', holder: existing };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Integrate with TaskClaimer: unclaim the task (best effort) (Req 2.5)
|
|
169
|
+
try {
|
|
170
|
+
await this._taskClaimer.unclaimTask(this._workspaceRoot, specName, taskId, agentId);
|
|
171
|
+
} catch (_unclaimErr) {
|
|
172
|
+
// Best effort – proceed with lock file deletion even if unclaim fails
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
await fs.unlink(lockPath);
|
|
177
|
+
} catch (err) {
|
|
178
|
+
if (err.code !== 'ENOENT') throw err;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return { success: true };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Release all task locks held by a specific agent.
|
|
186
|
+
* Scans all spec directories for `locks/` subdirectories.
|
|
187
|
+
*
|
|
188
|
+
* @param {string} agentId
|
|
189
|
+
* @returns {Promise<{released: Array<{specName: string, taskId: string}>}>}
|
|
190
|
+
*/
|
|
191
|
+
async releaseAllLocks(agentId) {
|
|
192
|
+
const released = [];
|
|
193
|
+
|
|
194
|
+
let specEntries;
|
|
195
|
+
try {
|
|
196
|
+
specEntries = await fs.readdir(this._specsDir, { withFileTypes: true });
|
|
197
|
+
} catch (err) {
|
|
198
|
+
if (err.code === 'ENOENT') return { released };
|
|
199
|
+
throw err;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
for (const entry of specEntries) {
|
|
203
|
+
if (!entry.isDirectory()) continue;
|
|
204
|
+
|
|
205
|
+
const specName = entry.name;
|
|
206
|
+
const locksDir = this._locksDir(specName);
|
|
207
|
+
|
|
208
|
+
let lockFiles;
|
|
209
|
+
try {
|
|
210
|
+
lockFiles = await fs.readdir(locksDir);
|
|
211
|
+
} catch (err) {
|
|
212
|
+
if (err.code === 'ENOENT') continue;
|
|
213
|
+
throw err;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for (const file of lockFiles) {
|
|
217
|
+
if (!file.endsWith('.lock')) continue;
|
|
218
|
+
|
|
219
|
+
const lockPath = path.join(locksDir, file);
|
|
220
|
+
const lockData = await this._readLockFile(lockPath);
|
|
221
|
+
|
|
222
|
+
if (lockData && lockData.agentId === agentId) {
|
|
223
|
+
try {
|
|
224
|
+
await fs.unlink(lockPath);
|
|
225
|
+
released.push({ specName, taskId: lockData.taskId });
|
|
226
|
+
} catch (err) {
|
|
227
|
+
if (err.code !== 'ENOENT') throw err;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return { released };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Get the lock status for a specific task.
|
|
238
|
+
*
|
|
239
|
+
* @param {string} specName
|
|
240
|
+
* @param {string} taskId
|
|
241
|
+
* @returns {Promise<{locked: boolean, lock?: object}>}
|
|
242
|
+
*/
|
|
243
|
+
async getTaskLockStatus(specName, taskId) {
|
|
244
|
+
const lockPath = this._lockFilePath(specName, taskId);
|
|
245
|
+
const lockData = await this._readLockFile(lockPath);
|
|
246
|
+
|
|
247
|
+
if (!lockData) {
|
|
248
|
+
return { locked: false, specName, taskId };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return { locked: true, specName, taskId, lock: lockData };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* List all locked tasks within a spec.
|
|
256
|
+
*
|
|
257
|
+
* @param {string} specName
|
|
258
|
+
* @returns {Promise<Array<{locked: boolean, specName: string, taskId: string, lock?: object}>>}
|
|
259
|
+
*/
|
|
260
|
+
async listLockedTasks(specName) {
|
|
261
|
+
const locksDir = this._locksDir(specName);
|
|
262
|
+
const results = [];
|
|
263
|
+
|
|
264
|
+
let files;
|
|
265
|
+
try {
|
|
266
|
+
files = await fs.readdir(locksDir);
|
|
267
|
+
} catch (err) {
|
|
268
|
+
if (err.code === 'ENOENT') return results;
|
|
269
|
+
throw err;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
for (const file of files) {
|
|
273
|
+
if (!file.endsWith('.lock')) continue;
|
|
274
|
+
|
|
275
|
+
const lockPath = path.join(locksDir, file);
|
|
276
|
+
const lockData = await this._readLockFile(lockPath);
|
|
277
|
+
|
|
278
|
+
if (lockData) {
|
|
279
|
+
results.push({
|
|
280
|
+
locked: true,
|
|
281
|
+
specName,
|
|
282
|
+
taskId: lockData.taskId,
|
|
283
|
+
lock: lockData,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return results;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ── Private helpers ──────────────────────────────────────────────────
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Build a lock record object.
|
|
295
|
+
* @private
|
|
296
|
+
*/
|
|
297
|
+
async _buildLockRecord(specName, taskId, agentId, reason) {
|
|
298
|
+
const machineInfo = await this._machineIdentifier.getMachineId();
|
|
299
|
+
return {
|
|
300
|
+
agentId,
|
|
301
|
+
machineId: machineInfo.id,
|
|
302
|
+
hostname: machineInfo.hostname,
|
|
303
|
+
taskId,
|
|
304
|
+
specName,
|
|
305
|
+
acquiredAt: new Date().toISOString(),
|
|
306
|
+
reason: reason || null,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Read and parse a lock file. Returns null if missing or corrupted.
|
|
312
|
+
* @param {string} lockPath
|
|
313
|
+
* @returns {Promise<object|null>}
|
|
314
|
+
* @private
|
|
315
|
+
*/
|
|
316
|
+
async _readLockFile(lockPath) {
|
|
317
|
+
try {
|
|
318
|
+
const raw = await fs.readFile(lockPath, 'utf8');
|
|
319
|
+
const data = JSON.parse(raw);
|
|
320
|
+
if (data && typeof data.agentId === 'string' && typeof data.taskId === 'string') {
|
|
321
|
+
return data;
|
|
322
|
+
}
|
|
323
|
+
return null; // Invalid structure
|
|
324
|
+
} catch (err) {
|
|
325
|
+
if (err.code === 'ENOENT' || err instanceof SyntaxError) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
throw err;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Atomically write a lock file using temp + rename (Req 2.7).
|
|
334
|
+
* @param {string} lockPath
|
|
335
|
+
* @param {object} data
|
|
336
|
+
* @returns {Promise<void>}
|
|
337
|
+
* @private
|
|
338
|
+
*/
|
|
339
|
+
async _atomicWriteLock(lockPath, data) {
|
|
340
|
+
const content = JSON.stringify(data, null, 2);
|
|
341
|
+
await fsUtils.atomicWrite(lockPath, content);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
module.exports = { TaskLockManager };
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit Logger
|
|
3
|
+
*
|
|
4
|
+
* Logs all AI operations with tamper-evident storage
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const crypto = require('crypto');
|
|
10
|
+
|
|
11
|
+
class AuditLogger {
|
|
12
|
+
constructor(projectRoot = process.cwd()) {
|
|
13
|
+
this.projectRoot = projectRoot;
|
|
14
|
+
this.auditPath = path.join(projectRoot, '.kiro/audit');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Log an operation
|
|
19
|
+
*
|
|
20
|
+
* @param {Object} entry - Audit entry
|
|
21
|
+
* @returns {Promise<string>} Entry ID
|
|
22
|
+
*/
|
|
23
|
+
async logOperation(entry) {
|
|
24
|
+
await fs.ensureDir(this.auditPath);
|
|
25
|
+
|
|
26
|
+
// Generate entry ID
|
|
27
|
+
const id = `audit-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
28
|
+
|
|
29
|
+
// Create full entry
|
|
30
|
+
const fullEntry = {
|
|
31
|
+
id,
|
|
32
|
+
timestamp: new Date().toISOString(),
|
|
33
|
+
...entry
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Calculate checksum for tamper-evidence
|
|
37
|
+
const checksum = this.calculateChecksum(fullEntry);
|
|
38
|
+
fullEntry.checksum = checksum;
|
|
39
|
+
|
|
40
|
+
// Write to log file (append)
|
|
41
|
+
const logFile = path.join(this.auditPath, 'operations.jsonl');
|
|
42
|
+
await fs.appendFile(
|
|
43
|
+
logFile,
|
|
44
|
+
JSON.stringify(fullEntry) + '\n',
|
|
45
|
+
'utf8'
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return id;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Query audit logs
|
|
53
|
+
*
|
|
54
|
+
* @param {Object} query - Query parameters
|
|
55
|
+
* @returns {Promise<Array>} Matching entries
|
|
56
|
+
*/
|
|
57
|
+
async queryLogs(query = {}) {
|
|
58
|
+
const logFile = path.join(this.auditPath, 'operations.jsonl');
|
|
59
|
+
|
|
60
|
+
if (!await fs.pathExists(logFile)) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Read all entries
|
|
65
|
+
const content = await fs.readFile(logFile, 'utf8');
|
|
66
|
+
const lines = content.trim().split('\n').filter(line => line);
|
|
67
|
+
const entries = lines.map(line => JSON.parse(line));
|
|
68
|
+
|
|
69
|
+
// Filter by query
|
|
70
|
+
return entries.filter(entry => {
|
|
71
|
+
if (query.projectName && entry.project !== query.projectName) return false;
|
|
72
|
+
if (query.operationType && entry.operationType !== query.operationType) return false;
|
|
73
|
+
if (query.outcome && entry.outcome !== query.outcome) return false;
|
|
74
|
+
if (query.environment && entry.securityEnvironment !== query.environment) return false;
|
|
75
|
+
|
|
76
|
+
if (query.fromDate) {
|
|
77
|
+
const entryDate = new Date(entry.timestamp);
|
|
78
|
+
const fromDate = new Date(query.fromDate);
|
|
79
|
+
if (entryDate < fromDate) return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (query.toDate) {
|
|
83
|
+
const entryDate = new Date(entry.timestamp);
|
|
84
|
+
const toDate = new Date(query.toDate);
|
|
85
|
+
if (entryDate > toDate) return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return true;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Generate audit summary
|
|
94
|
+
*
|
|
95
|
+
* @param {string} project - Project name
|
|
96
|
+
* @param {Object} timeRange - Time range {from, to}
|
|
97
|
+
* @returns {Promise<Object>} Audit summary
|
|
98
|
+
*/
|
|
99
|
+
async generateSummary(project, timeRange = {}) {
|
|
100
|
+
const entries = await this.queryLogs({
|
|
101
|
+
projectName: project,
|
|
102
|
+
fromDate: timeRange.from,
|
|
103
|
+
toDate: timeRange.to
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const summary = {
|
|
107
|
+
project,
|
|
108
|
+
timeRange,
|
|
109
|
+
totalOperations: entries.length,
|
|
110
|
+
successCount: entries.filter(e => e.outcome === 'success').length,
|
|
111
|
+
failureCount: entries.filter(e => e.outcome === 'failure').length,
|
|
112
|
+
operationsByType: {},
|
|
113
|
+
operationsByLevel: {}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
summary.successRate = summary.totalOperations > 0
|
|
117
|
+
? (summary.successCount / summary.totalOperations * 100).toFixed(2) + '%'
|
|
118
|
+
: '0%';
|
|
119
|
+
|
|
120
|
+
// Count by type
|
|
121
|
+
entries.forEach(entry => {
|
|
122
|
+
const type = entry.operationType || 'unknown';
|
|
123
|
+
summary.operationsByType[type] = (summary.operationsByType[type] || 0) + 1;
|
|
124
|
+
|
|
125
|
+
const level = entry.takeoverLevel || 'unknown';
|
|
126
|
+
summary.operationsByLevel[level] = (summary.operationsByLevel[level] || 0) + 1;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return summary;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Export audit logs
|
|
134
|
+
*
|
|
135
|
+
* @param {Object} query - Query parameters
|
|
136
|
+
* @param {string} format - Export format (json, csv)
|
|
137
|
+
* @returns {Promise<string>} Exported data
|
|
138
|
+
*/
|
|
139
|
+
async exportLogs(query = {}, format = 'json') {
|
|
140
|
+
const entries = await this.queryLogs(query);
|
|
141
|
+
|
|
142
|
+
if (format === 'json') {
|
|
143
|
+
return JSON.stringify(entries, null, 2);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (format === 'csv') {
|
|
147
|
+
if (entries.length === 0) return '';
|
|
148
|
+
|
|
149
|
+
// CSV header
|
|
150
|
+
const headers = Object.keys(entries[0]);
|
|
151
|
+
let csv = headers.join(',') + '\n';
|
|
152
|
+
|
|
153
|
+
// CSV rows
|
|
154
|
+
entries.forEach(entry => {
|
|
155
|
+
const row = headers.map(h => {
|
|
156
|
+
const value = entry[h];
|
|
157
|
+
return typeof value === 'string' ? `"${value}"` : value;
|
|
158
|
+
});
|
|
159
|
+
csv += row.join(',') + '\n';
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return csv;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
throw new Error(`Unsupported export format: ${format}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Calculate checksum for tamper-evidence
|
|
170
|
+
*
|
|
171
|
+
* @param {Object} entry - Audit entry
|
|
172
|
+
* @returns {string} SHA-256 checksum
|
|
173
|
+
*/
|
|
174
|
+
calculateChecksum(entry) {
|
|
175
|
+
const data = JSON.stringify(entry);
|
|
176
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Verify entry integrity
|
|
181
|
+
*
|
|
182
|
+
* @param {Object} entry - Audit entry with checksum
|
|
183
|
+
* @returns {boolean} Whether entry is valid
|
|
184
|
+
*/
|
|
185
|
+
verifyEntry(entry) {
|
|
186
|
+
const { checksum, ...entryWithoutChecksum } = entry;
|
|
187
|
+
const calculatedChecksum = this.calculateChecksum(entryWithoutChecksum);
|
|
188
|
+
return checksum === calculatedChecksum;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Flag anomalies in operations
|
|
193
|
+
*
|
|
194
|
+
* @param {string} project - Project name
|
|
195
|
+
* @param {Object} threshold - Anomaly thresholds
|
|
196
|
+
* @returns {Promise<Array>} Detected anomalies
|
|
197
|
+
*/
|
|
198
|
+
async flagAnomalies(project, threshold = {}) {
|
|
199
|
+
const entries = await this.queryLogs({ projectName: project });
|
|
200
|
+
|
|
201
|
+
if (entries.length === 0) {
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const anomalies = [];
|
|
206
|
+
|
|
207
|
+
// Default thresholds
|
|
208
|
+
const thresholds = {
|
|
209
|
+
errorRatePercent: threshold.errorRatePercent || 10,
|
|
210
|
+
operationCountPerHour: threshold.operationCountPerHour || 100,
|
|
211
|
+
unusualOperationType: threshold.unusualOperationType || true,
|
|
212
|
+
...threshold
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// Calculate baseline metrics
|
|
216
|
+
const totalOps = entries.length;
|
|
217
|
+
const failedOps = entries.filter(e => e.outcome === 'failure').length;
|
|
218
|
+
const errorRate = (failedOps / totalOps) * 100;
|
|
219
|
+
|
|
220
|
+
// Anomaly 1: High error rate
|
|
221
|
+
if (errorRate > thresholds.errorRatePercent) {
|
|
222
|
+
anomalies.push({
|
|
223
|
+
type: 'high_error_rate',
|
|
224
|
+
severity: 'high',
|
|
225
|
+
message: `Error rate ${errorRate.toFixed(2)}% exceeds threshold ${thresholds.errorRatePercent}%`,
|
|
226
|
+
metric: errorRate,
|
|
227
|
+
threshold: thresholds.errorRatePercent,
|
|
228
|
+
affectedOperations: failedOps
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Anomaly 2: Unusual operation frequency
|
|
233
|
+
const now = new Date();
|
|
234
|
+
const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
|
|
235
|
+
const recentOps = entries.filter(e => new Date(e.timestamp) > oneHourAgo);
|
|
236
|
+
|
|
237
|
+
if (recentOps.length > thresholds.operationCountPerHour) {
|
|
238
|
+
anomalies.push({
|
|
239
|
+
type: 'high_operation_frequency',
|
|
240
|
+
severity: 'medium',
|
|
241
|
+
message: `${recentOps.length} operations in last hour exceeds threshold ${thresholds.operationCountPerHour}`,
|
|
242
|
+
metric: recentOps.length,
|
|
243
|
+
threshold: thresholds.operationCountPerHour,
|
|
244
|
+
timeWindow: '1 hour'
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Anomaly 3: Unusual operation types
|
|
249
|
+
if (thresholds.unusualOperationType) {
|
|
250
|
+
const operationTypes = {};
|
|
251
|
+
entries.forEach(e => {
|
|
252
|
+
const type = e.operationType || 'unknown';
|
|
253
|
+
operationTypes[type] = (operationTypes[type] || 0) + 1;
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Flag operation types that appear only once or twice (potentially unusual)
|
|
257
|
+
Object.entries(operationTypes).forEach(([type, count]) => {
|
|
258
|
+
if (count <= 2 && totalOps > 10) {
|
|
259
|
+
anomalies.push({
|
|
260
|
+
type: 'unusual_operation_type',
|
|
261
|
+
severity: 'low',
|
|
262
|
+
message: `Operation type '${type}' appears only ${count} time(s)`,
|
|
263
|
+
operationType: type,
|
|
264
|
+
occurrences: count
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Anomaly 4: Repeated failures of same operation
|
|
271
|
+
const failuresByType = {};
|
|
272
|
+
entries.filter(e => e.outcome === 'failure').forEach(e => {
|
|
273
|
+
const type = e.operationType || 'unknown';
|
|
274
|
+
failuresByType[type] = (failuresByType[type] || 0) + 1;
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
Object.entries(failuresByType).forEach(([type, count]) => {
|
|
278
|
+
if (count >= 3) {
|
|
279
|
+
anomalies.push({
|
|
280
|
+
type: 'repeated_failures',
|
|
281
|
+
severity: 'high',
|
|
282
|
+
message: `Operation type '${type}' failed ${count} times`,
|
|
283
|
+
operationType: type,
|
|
284
|
+
failureCount: count
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
return anomalies;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
module.exports = AuditLogger;
|