@red-codes/core 1.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/LICENSE +190 -0
- package/dist/actions.d.ts +11 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +76 -0
- package/dist/actions.js.map +1 -0
- package/dist/adapters.d.ts +19 -0
- package/dist/adapters.d.ts.map +1 -0
- package/dist/adapters.js +85 -0
- package/dist/adapters.js.map +1 -0
- package/dist/crypto-hash.d.ts +11 -0
- package/dist/crypto-hash.d.ts.map +1 -0
- package/dist/crypto-hash.js +14 -0
- package/dist/crypto-hash.js.map +1 -0
- package/dist/data/actions.json +42 -0
- package/dist/data/blast-radius.json +36 -0
- package/dist/data/destructive-patterns.json +94 -0
- package/dist/data/escalation.json +13 -0
- package/dist/data/git-action-patterns.json +7 -0
- package/dist/data/invariant-patterns.json +48 -0
- package/dist/data/tool-action-map.json +14 -0
- package/dist/execution-log/bridge.d.ts +12 -0
- package/dist/execution-log/bridge.d.ts.map +1 -0
- package/dist/execution-log/bridge.js +112 -0
- package/dist/execution-log/bridge.js.map +1 -0
- package/dist/execution-log/event-log.d.ts +7 -0
- package/dist/execution-log/event-log.d.ts.map +1 -0
- package/dist/execution-log/event-log.js +103 -0
- package/dist/execution-log/event-log.js.map +1 -0
- package/dist/execution-log/event-projections.d.ts +28 -0
- package/dist/execution-log/event-projections.d.ts.map +1 -0
- package/dist/execution-log/event-projections.js +272 -0
- package/dist/execution-log/event-projections.js.map +1 -0
- package/dist/execution-log/event-schema.d.ts +56 -0
- package/dist/execution-log/event-schema.d.ts.map +1 -0
- package/dist/execution-log/event-schema.js +160 -0
- package/dist/execution-log/event-schema.js.map +1 -0
- package/dist/execution-log/index.d.ts +7 -0
- package/dist/execution-log/index.d.ts.map +1 -0
- package/dist/execution-log/index.js +13 -0
- package/dist/execution-log/index.js.map +1 -0
- package/dist/governance-data.d.ts +177 -0
- package/dist/governance-data.d.ts.map +1 -0
- package/dist/governance-data.js +53 -0
- package/dist/governance-data.js.map +1 -0
- package/dist/hash.d.ts +5 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +13 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/persona.d.ts +32 -0
- package/dist/persona.d.ts.map +1 -0
- package/dist/persona.js +138 -0
- package/dist/persona.js.map +1 -0
- package/dist/repo-root.d.ts +15 -0
- package/dist/repo-root.d.ts.map +1 -0
- package/dist/repo-root.js +56 -0
- package/dist/repo-root.js.map +1 -0
- package/dist/rng.d.ts +29 -0
- package/dist/rng.d.ts.map +1 -0
- package/dist/rng.js +48 -0
- package/dist/rng.js.map +1 -0
- package/dist/rtk.d.ts +22 -0
- package/dist/rtk.d.ts.map +1 -0
- package/dist/rtk.js +61 -0
- package/dist/rtk.js.map +1 -0
- package/dist/trust-store.d.ts +19 -0
- package/dist/trust-store.d.ts.map +1 -0
- package/dist/trust-store.js +98 -0
- package/dist/trust-store.js.map +1 -0
- package/dist/types.d.ts +849 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +34 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
[
|
|
2
|
+
{ "patterns": ["\\bgit\\s+push\\s+--force\\b", "\\bgit\\s+push\\s+-f\\b"], "actionType": "git.force-push" },
|
|
3
|
+
{ "patterns": ["\\bgit\\s+push\\b"], "actionType": "git.push" },
|
|
4
|
+
{ "patterns": ["\\bgit\\s+branch\\s+-[dD]\\b"], "actionType": "git.branch.delete" },
|
|
5
|
+
{ "patterns": ["\\bgit\\s+merge\\b"], "actionType": "git.merge" },
|
|
6
|
+
{ "patterns": ["\\bgit\\s+commit\\b"], "actionType": "git.commit" }
|
|
7
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sensitiveFilePatterns": [
|
|
3
|
+
".env", "credentials", ".pem", ".key", "secret", "token",
|
|
4
|
+
".npmrc", ".netrc", ".pgpass", ".htpasswd",
|
|
5
|
+
"id_rsa", "id_ed25519", "id_ecdsa", "id_dsa",
|
|
6
|
+
".p12", ".pfx", ".jks", "keystore",
|
|
7
|
+
"secrets.yaml", "secrets.yml", "vault.json"
|
|
8
|
+
],
|
|
9
|
+
"credentialPathPatterns": [
|
|
10
|
+
"/.ssh/", "\\.ssh\\",
|
|
11
|
+
"/.aws/credentials", "/.aws/config", "\\.aws\\credentials", "\\.aws\\config",
|
|
12
|
+
"/.config/gcloud/", "\\.config\\gcloud\\",
|
|
13
|
+
"/.azure/", "\\.azure\\",
|
|
14
|
+
"/.docker/config.json", "\\.docker\\config.json"
|
|
15
|
+
],
|
|
16
|
+
"credentialBasenamePatterns": [".npmrc", ".pypirc", ".netrc", ".curlrc"],
|
|
17
|
+
"containerConfigBasenames": [
|
|
18
|
+
"dockerfile", "docker-compose.yml", "docker-compose.yaml",
|
|
19
|
+
"compose.yml", "compose.yaml", ".dockerignore", "containerfile"
|
|
20
|
+
],
|
|
21
|
+
"lifecycleScripts": [
|
|
22
|
+
"preinstall", "postinstall", "prepare", "prepublishOnly",
|
|
23
|
+
"prepack", "postpack", "install"
|
|
24
|
+
],
|
|
25
|
+
"envFileRegex": "(?:^|[\\\\/])\\.env(?:\\.\\w+)?$",
|
|
26
|
+
"dockerfileSuffixRegex": "\\.dockerfile$",
|
|
27
|
+
"invariants": [
|
|
28
|
+
{ "id": "no-secret-exposure", "name": "No Secret Exposure", "description": "Sensitive files (.env, credentials, keys) must not be committed or exposed", "severity": 5 },
|
|
29
|
+
{ "id": "protected-branch", "name": "Protected Branch Safety", "description": "Direct pushes to main/master are forbidden", "severity": 4 },
|
|
30
|
+
{ "id": "blast-radius-limit", "name": "Blast Radius Limit", "description": "A single operation must not modify too many files at once", "severity": 3 },
|
|
31
|
+
{ "id": "test-before-push", "name": "Tests Before Push", "description": "Tests must pass before pushing code", "severity": 3 },
|
|
32
|
+
{ "id": "no-force-push", "name": "No Force Push", "description": "Force pushes are forbidden unless explicitly authorized", "severity": 4 },
|
|
33
|
+
{ "id": "no-skill-modification", "name": "No Skill Modification", "description": "Agent skill files (.claude/skills/) must not be modified by governed actions", "severity": 4 },
|
|
34
|
+
{ "id": "no-scheduled-task-modification", "name": "No Scheduled Task Modification", "description": "Agents must not modify scheduled task definitions (.claude/scheduled-tasks/) directly", "severity": 5 },
|
|
35
|
+
{ "id": "no-credential-file-creation", "name": "No Credential File Creation", "description": "Agents must not create or overwrite well-known credential files (SSH keys, cloud configs, auth tokens)", "severity": 5 },
|
|
36
|
+
{ "id": "no-package-script-injection", "name": "No Package Script Injection", "description": "Modifications to package.json scripts are flagged as potential supply chain attack vectors", "severity": 4 },
|
|
37
|
+
{ "id": "recursive-operation-guard", "name": "Recursive Operation Guard", "description": "Flags recursive operations (find -exec, xargs) combined with write/delete operations that could cause widespread damage", "severity": 2 },
|
|
38
|
+
{ "id": "large-file-write", "name": "Large File Write Limit", "description": "Single file writes must not exceed a size threshold to prevent data dumps or runaway generation", "severity": 3 },
|
|
39
|
+
{ "id": "no-cicd-config-modification", "name": "No CI/CD Config Modification", "description": "CI/CD pipeline configurations must not be modified by governed actions — prevents supply chain attacks via malicious build steps", "severity": 5 },
|
|
40
|
+
{ "id": "no-permission-escalation", "name": "No Permission Escalation", "description": "Agents must not escalate filesystem permissions (world-writable, setuid/setgid, ownership changes, sudoers)", "severity": 4 },
|
|
41
|
+
{ "id": "no-governance-self-modification", "name": "No Governance Self-Modification", "description": "Agents must not modify governance configuration (policy files, governance data, policy packs)", "severity": 5 },
|
|
42
|
+
{ "id": "lockfile-integrity", "name": "Lockfile Integrity", "description": "Package lockfiles must stay in sync with manifests", "severity": 2 },
|
|
43
|
+
{ "id": "no-container-config-modification", "name": "No Container Config Modification", "description": "Container configuration files (Dockerfile, docker-compose, .dockerignore, Containerfile) must not be modified without authorization", "severity": 3 },
|
|
44
|
+
{ "id": "no-env-var-modification", "name": "No Environment Variable Modification", "description": "Detects attempts to modify environment variables or shell profile files — environment variables often contain secrets and profile modifications can establish persistent backdoors", "severity": 3 },
|
|
45
|
+
{ "id": "no-destructive-migration", "name": "No Destructive Migration", "description": "Detects potentially destructive database migration files — flags writes to migration directories containing DROP, TRUNCATE, or other destructive DDL", "severity": 3 },
|
|
46
|
+
{ "id": "transitive-effect-analysis", "name": "Transitive Effect Analysis", "description": "Detects when an agent writes a script or config file whose contents would produce effects that would be denied if executed directly — closes the creative circumvention gap", "severity": 4 }
|
|
47
|
+
]
|
|
48
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Write": "file.write",
|
|
3
|
+
"Edit": "file.write",
|
|
4
|
+
"Read": "file.read",
|
|
5
|
+
"Bash": "shell.exec",
|
|
6
|
+
"Glob": "file.read",
|
|
7
|
+
"Grep": "file.read",
|
|
8
|
+
"NotebookEdit": "file.write",
|
|
9
|
+
"TodoWrite": "shell.exec",
|
|
10
|
+
"WebFetch": "http.request",
|
|
11
|
+
"WebSearch": "http.request",
|
|
12
|
+
"Agent": "shell.exec",
|
|
13
|
+
"Skill": "shell.exec"
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DomainEvent, ExecutionEvent, ExecutionEventLog } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Convert a domain event into an execution event.
|
|
4
|
+
* Returns null if the domain event kind has no execution event mapping.
|
|
5
|
+
*/
|
|
6
|
+
export declare function domainEventToExecutionEvent(event: DomainEvent): ExecutionEvent | null;
|
|
7
|
+
/**
|
|
8
|
+
* Create a bridge that automatically records domain events as execution events.
|
|
9
|
+
* Returns a subscriber function compatible with the domain event system.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createEventBridge(log: ExecutionEventLog): (event: DomainEvent) => void;
|
|
12
|
+
//# sourceMappingURL=bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../src/execution-log/bridge.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACd,iBAAiB,EAIlB,MAAM,aAAa,CAAC;AA+ErB;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,WAAW,GAAG,cAAc,GAAG,IAAI,CA0BrF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAOtF"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Execution Event Log Bridge — connects execution events to the domain event system.
|
|
2
|
+
// Maps domain events to execution events and vice versa.
|
|
3
|
+
// No DOM, no Node.js APIs — pure domain logic.
|
|
4
|
+
import { createExecutionEvent } from './event-schema.js';
|
|
5
|
+
import { AGENT_EDIT_FILE, RUNTIME_EXCEPTION, TEST_SUITE_FAILED, TEST_SUITE_PASSED, BUILD_FAILED, BUILD_SUCCEEDED, DEPLOYMENT_FAILED, DEPLOYMENT_SUCCEEDED, POLICY_VIOLATION_DETECTED, INVARIANT_CHECK_FAILED, } from './event-schema.js';
|
|
6
|
+
// --- Domain Event → Execution Event Kind Mapping ---
|
|
7
|
+
const DOMAIN_TO_EXECUTION_KIND = {
|
|
8
|
+
ActionExecuted: AGENT_EDIT_FILE,
|
|
9
|
+
ActionFailed: RUNTIME_EXCEPTION,
|
|
10
|
+
ErrorObserved: RUNTIME_EXCEPTION,
|
|
11
|
+
TestCompleted: TEST_SUITE_PASSED,
|
|
12
|
+
BuildCompleted: BUILD_SUCCEEDED,
|
|
13
|
+
DeployCompleted: DEPLOYMENT_SUCCEEDED,
|
|
14
|
+
PolicyDenied: POLICY_VIOLATION_DETECTED,
|
|
15
|
+
InvariantViolation: INVARIANT_CHECK_FAILED,
|
|
16
|
+
};
|
|
17
|
+
// Override with failure kinds when the domain event indicates failure
|
|
18
|
+
const FAILURE_OVERRIDES = {
|
|
19
|
+
TestCompleted: (event) => (event.result === 'fail' ? TEST_SUITE_FAILED : null),
|
|
20
|
+
BuildCompleted: (event) => (event.result === 'fail' ? BUILD_FAILED : null),
|
|
21
|
+
DeployCompleted: (event) => (event.result === 'fail' ? DEPLOYMENT_FAILED : null),
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Infer actor from a domain event.
|
|
25
|
+
*/
|
|
26
|
+
function inferActor(event) {
|
|
27
|
+
if (event.agentId || event.kind === 'ActionExecuted' || event.kind === 'ActionFailed') {
|
|
28
|
+
return 'agent';
|
|
29
|
+
}
|
|
30
|
+
if (event.kind === 'PolicyDenied' ||
|
|
31
|
+
event.kind === 'InvariantViolation' ||
|
|
32
|
+
event.kind === 'BlastRadiusExceeded') {
|
|
33
|
+
return 'system';
|
|
34
|
+
}
|
|
35
|
+
return 'human';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Infer event source from a domain event kind.
|
|
39
|
+
*/
|
|
40
|
+
function inferSource(event) {
|
|
41
|
+
const kind = event.kind;
|
|
42
|
+
if (kind === 'TestCompleted' || kind === 'BuildCompleted')
|
|
43
|
+
return 'ci';
|
|
44
|
+
if (kind === 'CommitCreated' || kind === 'FileSaved')
|
|
45
|
+
return 'git';
|
|
46
|
+
if (kind === 'DeployCompleted')
|
|
47
|
+
return 'runtime';
|
|
48
|
+
if (kind === 'PolicyDenied' || kind === 'InvariantViolation' || kind === 'BlastRadiusExceeded') {
|
|
49
|
+
return 'governance';
|
|
50
|
+
}
|
|
51
|
+
if (kind === 'ErrorObserved')
|
|
52
|
+
return 'runtime';
|
|
53
|
+
return 'cli';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Build execution context from a domain event.
|
|
57
|
+
*/
|
|
58
|
+
function buildContext(event) {
|
|
59
|
+
const context = {};
|
|
60
|
+
if (typeof event.file === 'string')
|
|
61
|
+
context.file = event.file;
|
|
62
|
+
if (typeof event.branch === 'string')
|
|
63
|
+
context.branch = event.branch;
|
|
64
|
+
if (typeof event.hash === 'string')
|
|
65
|
+
context.commit = event.hash;
|
|
66
|
+
if (typeof event.agentId === 'string')
|
|
67
|
+
context.agentRunId = event.agentId;
|
|
68
|
+
return context;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Convert a domain event into an execution event.
|
|
72
|
+
* Returns null if the domain event kind has no execution event mapping.
|
|
73
|
+
*/
|
|
74
|
+
export function domainEventToExecutionEvent(event) {
|
|
75
|
+
let executionKind = DOMAIN_TO_EXECUTION_KIND[event.kind];
|
|
76
|
+
if (!executionKind)
|
|
77
|
+
return null;
|
|
78
|
+
// Check for failure overrides
|
|
79
|
+
const override = FAILURE_OVERRIDES[event.kind];
|
|
80
|
+
if (override) {
|
|
81
|
+
const overrideKind = override(event);
|
|
82
|
+
if (overrideKind)
|
|
83
|
+
executionKind = overrideKind;
|
|
84
|
+
}
|
|
85
|
+
// Build payload from domain event data (exclude standard fields)
|
|
86
|
+
const payload = {};
|
|
87
|
+
const standardFields = new Set(['id', 'kind', 'timestamp', 'fingerprint']);
|
|
88
|
+
for (const [key, value] of Object.entries(event)) {
|
|
89
|
+
if (!standardFields.has(key)) {
|
|
90
|
+
payload[key] = value;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return createExecutionEvent(executionKind, {
|
|
94
|
+
actor: inferActor(event),
|
|
95
|
+
source: inferSource(event),
|
|
96
|
+
context: buildContext(event),
|
|
97
|
+
payload,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Create a bridge that automatically records domain events as execution events.
|
|
102
|
+
* Returns a subscriber function compatible with the domain event system.
|
|
103
|
+
*/
|
|
104
|
+
export function createEventBridge(log) {
|
|
105
|
+
return (event) => {
|
|
106
|
+
const executionEvent = domainEventToExecutionEvent(event);
|
|
107
|
+
if (executionEvent) {
|
|
108
|
+
log.append(executionEvent);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../../src/execution-log/bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ExecutionEventLog } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create an in-memory execution event log.
|
|
4
|
+
* Append-only with support for causal chain tracing and NDJSON persistence.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createExecutionEventLog(): ExecutionEventLog;
|
|
7
|
+
//# sourceMappingURL=event-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-log.d.ts","sourceRoot":"","sources":["../../src/execution-log/event-log.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAwC,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAG3F;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,iBAAiB,CAqG3D"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Execution Event Log — append-only event log with causal chain support.
|
|
2
|
+
// Stores execution events and supports query, replay, trace, and NDJSON serialization.
|
|
3
|
+
// No DOM, no Node.js APIs — pure domain logic.
|
|
4
|
+
import { validateExecutionEvent } from './event-schema.js';
|
|
5
|
+
/**
|
|
6
|
+
* Create an in-memory execution event log.
|
|
7
|
+
* Append-only with support for causal chain tracing and NDJSON persistence.
|
|
8
|
+
*/
|
|
9
|
+
export function createExecutionEventLog() {
|
|
10
|
+
let events = [];
|
|
11
|
+
const indexById = new Map();
|
|
12
|
+
function rebuildIndex() {
|
|
13
|
+
indexById.clear();
|
|
14
|
+
for (let i = 0; i < events.length; i++) {
|
|
15
|
+
indexById.set(events[i].id, i);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
append(event) {
|
|
20
|
+
const { valid, errors } = validateExecutionEvent(event);
|
|
21
|
+
if (!valid) {
|
|
22
|
+
throw new Error(`Cannot append invalid execution event: ${errors.join('; ')}`);
|
|
23
|
+
}
|
|
24
|
+
if (event.causedBy && !indexById.has(event.causedBy)) {
|
|
25
|
+
// causedBy references must point to events already in the log,
|
|
26
|
+
// or to events from a previous session (allow unknown references)
|
|
27
|
+
}
|
|
28
|
+
indexById.set(event.id, events.length);
|
|
29
|
+
events.push(event);
|
|
30
|
+
},
|
|
31
|
+
query(filter = {}) {
|
|
32
|
+
let result = events;
|
|
33
|
+
if (filter.kind) {
|
|
34
|
+
result = result.filter((e) => e.kind === filter.kind);
|
|
35
|
+
}
|
|
36
|
+
if (filter.actor) {
|
|
37
|
+
result = result.filter((e) => e.actor === filter.actor);
|
|
38
|
+
}
|
|
39
|
+
if (filter.source) {
|
|
40
|
+
result = result.filter((e) => e.source === filter.source);
|
|
41
|
+
}
|
|
42
|
+
if (filter.since !== undefined) {
|
|
43
|
+
result = result.filter((e) => e.timestamp >= filter.since);
|
|
44
|
+
}
|
|
45
|
+
if (filter.until !== undefined) {
|
|
46
|
+
result = result.filter((e) => e.timestamp <= filter.until);
|
|
47
|
+
}
|
|
48
|
+
if (filter.agentRunId) {
|
|
49
|
+
result = result.filter((e) => e.context.agentRunId === filter.agentRunId);
|
|
50
|
+
}
|
|
51
|
+
if (filter.file) {
|
|
52
|
+
result = result.filter((e) => e.context.file === filter.file);
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
},
|
|
56
|
+
replay(fromId) {
|
|
57
|
+
if (!fromId)
|
|
58
|
+
return [...events];
|
|
59
|
+
const idx = indexById.get(fromId);
|
|
60
|
+
if (idx === undefined)
|
|
61
|
+
return [];
|
|
62
|
+
return events.slice(idx);
|
|
63
|
+
},
|
|
64
|
+
trace(eventId) {
|
|
65
|
+
const chain = [];
|
|
66
|
+
const visited = new Set();
|
|
67
|
+
let currentId = eventId;
|
|
68
|
+
while (currentId && !visited.has(currentId)) {
|
|
69
|
+
visited.add(currentId);
|
|
70
|
+
const idx = indexById.get(currentId);
|
|
71
|
+
if (idx === undefined)
|
|
72
|
+
break;
|
|
73
|
+
const event = events[idx];
|
|
74
|
+
chain.unshift(event);
|
|
75
|
+
currentId = event.causedBy;
|
|
76
|
+
}
|
|
77
|
+
return chain;
|
|
78
|
+
},
|
|
79
|
+
count() {
|
|
80
|
+
return events.length;
|
|
81
|
+
},
|
|
82
|
+
clear() {
|
|
83
|
+
events = [];
|
|
84
|
+
indexById.clear();
|
|
85
|
+
},
|
|
86
|
+
toNDJSON() {
|
|
87
|
+
return events.map((e) => JSON.stringify(e)).join('\n');
|
|
88
|
+
},
|
|
89
|
+
fromNDJSON(ndjson) {
|
|
90
|
+
const lines = ndjson.split('\n').filter((line) => line.trim().length > 0);
|
|
91
|
+
let loaded = 0;
|
|
92
|
+
for (const line of lines) {
|
|
93
|
+
const parsed = JSON.parse(line);
|
|
94
|
+
indexById.set(parsed.id, events.length);
|
|
95
|
+
events.push(parsed);
|
|
96
|
+
loaded++;
|
|
97
|
+
}
|
|
98
|
+
rebuildIndex();
|
|
99
|
+
return loaded;
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=event-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-log.js","sourceRoot":"","sources":["../../src/execution-log/event-log.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ExecutionEvent, ExecutionEventLog, FailureCluster, EncounterMapping, RiskScore } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build the full causal chain leading to an event.
|
|
4
|
+
* Walks `causedBy` references back to the root cause.
|
|
5
|
+
* Returns events in chronological order (root first).
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildCausalChain(log: ExecutionEventLog, eventId: string): ExecutionEvent[];
|
|
8
|
+
/**
|
|
9
|
+
* Score an agent run by its risk profile.
|
|
10
|
+
* Higher score = more risk. Based on failures, violations,
|
|
11
|
+
* skipped tests, sensitive file edits, and action velocity.
|
|
12
|
+
*/
|
|
13
|
+
export declare function scoreAgentRun(log: ExecutionEventLog, agentRunId: string): RiskScore;
|
|
14
|
+
export interface ClusterOptions {
|
|
15
|
+
readonly windowMs?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Cluster related failures by file and time proximity.
|
|
19
|
+
* Groups failures that share a common file or occur within a time window.
|
|
20
|
+
*/
|
|
21
|
+
export declare function clusterFailures(log: ExecutionEventLog, options?: ClusterOptions): FailureCluster[];
|
|
22
|
+
/**
|
|
23
|
+
* Map an execution event to a game encounter.
|
|
24
|
+
* Only failure events produce encounters.
|
|
25
|
+
* Returns null for non-failure events.
|
|
26
|
+
*/
|
|
27
|
+
export declare function mapToEncounter(event: ExecutionEvent): EncounterMapping | null;
|
|
28
|
+
//# sourceMappingURL=event-projections.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-projections.d.ts","sourceRoot":"","sources":["../../src/execution-log/event-projections.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,SAAS,EAGV,MAAM,aAAa,CAAC;AAerB;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAE1F;AA6BD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,CAsFnF;AAID,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,iBAAiB,EACtB,OAAO,GAAE,cAAmB,GAC3B,cAAc,EAAE,CA0FlB;AA8CD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB,GAAG,IAAI,CAe7E"}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
// Execution Event Projections — derived views from the execution event stream.
|
|
2
|
+
// Causal chain construction, risk scoring, failure clustering, encounter mapping.
|
|
3
|
+
// No DOM, no Node.js APIs — pure domain logic.
|
|
4
|
+
import { simpleHash } from '../hash.js';
|
|
5
|
+
import { FAILURE_KINDS, VIOLATION_KINDS, AGENT_ACTION_KINDS, TESTS_SKIPPED, RUNTIME_EXCEPTION, TEST_SUITE_FAILED, DEPLOYMENT_FAILED, BUILD_FAILED, } from './event-schema.js';
|
|
6
|
+
// --- Causal Chain ---
|
|
7
|
+
/**
|
|
8
|
+
* Build the full causal chain leading to an event.
|
|
9
|
+
* Walks `causedBy` references back to the root cause.
|
|
10
|
+
* Returns events in chronological order (root first).
|
|
11
|
+
*/
|
|
12
|
+
export function buildCausalChain(log, eventId) {
|
|
13
|
+
return log.trace(eventId);
|
|
14
|
+
}
|
|
15
|
+
// --- Risk Scoring ---
|
|
16
|
+
const RISK_WEIGHTS = {
|
|
17
|
+
failure: 10,
|
|
18
|
+
violation: 25,
|
|
19
|
+
skippedTests: 15,
|
|
20
|
+
sensitiveFileEdit: 20,
|
|
21
|
+
highActionRate: 5,
|
|
22
|
+
};
|
|
23
|
+
const SENSITIVE_PATTERNS = [
|
|
24
|
+
/auth/i,
|
|
25
|
+
/security/i,
|
|
26
|
+
/password/i,
|
|
27
|
+
/secret/i,
|
|
28
|
+
/key/i,
|
|
29
|
+
/token/i,
|
|
30
|
+
/credential/i,
|
|
31
|
+
/\.env/i,
|
|
32
|
+
/migration/i,
|
|
33
|
+
/deploy/i,
|
|
34
|
+
];
|
|
35
|
+
function isSensitiveFile(file) {
|
|
36
|
+
return SENSITIVE_PATTERNS.some((p) => p.test(file));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Score an agent run by its risk profile.
|
|
40
|
+
* Higher score = more risk. Based on failures, violations,
|
|
41
|
+
* skipped tests, sensitive file edits, and action velocity.
|
|
42
|
+
*/
|
|
43
|
+
export function scoreAgentRun(log, agentRunId) {
|
|
44
|
+
const events = log.query({ agentRunId });
|
|
45
|
+
const factors = [];
|
|
46
|
+
let totalScore = 0;
|
|
47
|
+
let failureCount = 0;
|
|
48
|
+
let violationCount = 0;
|
|
49
|
+
// Count failures
|
|
50
|
+
const failures = events.filter((e) => FAILURE_KINDS.has(e.kind));
|
|
51
|
+
failureCount = failures.length;
|
|
52
|
+
if (failureCount > 0) {
|
|
53
|
+
const weight = failureCount * RISK_WEIGHTS.failure;
|
|
54
|
+
totalScore += weight;
|
|
55
|
+
factors.push({
|
|
56
|
+
name: 'failures',
|
|
57
|
+
weight,
|
|
58
|
+
detail: `${failureCount} failure(s) during agent run`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
// Count violations
|
|
62
|
+
const violations = events.filter((e) => VIOLATION_KINDS.has(e.kind));
|
|
63
|
+
violationCount = violations.length;
|
|
64
|
+
if (violationCount > 0) {
|
|
65
|
+
const weight = violationCount * RISK_WEIGHTS.violation;
|
|
66
|
+
totalScore += weight;
|
|
67
|
+
factors.push({
|
|
68
|
+
name: 'violations',
|
|
69
|
+
weight,
|
|
70
|
+
detail: `${violationCount} policy/invariant violation(s)`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// Skipped tests
|
|
74
|
+
const skipped = events.filter((e) => e.kind === TESTS_SKIPPED);
|
|
75
|
+
if (skipped.length > 0) {
|
|
76
|
+
totalScore += RISK_WEIGHTS.skippedTests;
|
|
77
|
+
factors.push({
|
|
78
|
+
name: 'skipped_tests',
|
|
79
|
+
weight: RISK_WEIGHTS.skippedTests,
|
|
80
|
+
detail: `Tests were skipped ${skipped.length} time(s)`,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// Sensitive file edits
|
|
84
|
+
const agentActions = events.filter((e) => AGENT_ACTION_KINDS.has(e.kind));
|
|
85
|
+
const sensitiveEdits = agentActions.filter((e) => e.context.file && isSensitiveFile(e.context.file));
|
|
86
|
+
if (sensitiveEdits.length > 0) {
|
|
87
|
+
const weight = sensitiveEdits.length * RISK_WEIGHTS.sensitiveFileEdit;
|
|
88
|
+
totalScore += weight;
|
|
89
|
+
factors.push({
|
|
90
|
+
name: 'sensitive_file_edits',
|
|
91
|
+
weight,
|
|
92
|
+
detail: `${sensitiveEdits.length} edit(s) to sensitive files`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// High action rate (>50 actions in a single run is suspicious)
|
|
96
|
+
if (agentActions.length > 50) {
|
|
97
|
+
totalScore += RISK_WEIGHTS.highActionRate;
|
|
98
|
+
factors.push({
|
|
99
|
+
name: 'high_action_rate',
|
|
100
|
+
weight: RISK_WEIGHTS.highActionRate,
|
|
101
|
+
detail: `${agentActions.length} agent actions in a single run`,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
// Determine risk level
|
|
105
|
+
let level;
|
|
106
|
+
if (totalScore >= 75)
|
|
107
|
+
level = 'critical';
|
|
108
|
+
else if (totalScore >= 40)
|
|
109
|
+
level = 'high';
|
|
110
|
+
else if (totalScore >= 15)
|
|
111
|
+
level = 'medium';
|
|
112
|
+
else
|
|
113
|
+
level = 'low';
|
|
114
|
+
return {
|
|
115
|
+
agentRunId,
|
|
116
|
+
score: totalScore,
|
|
117
|
+
level,
|
|
118
|
+
factors,
|
|
119
|
+
eventCount: events.length,
|
|
120
|
+
failureCount,
|
|
121
|
+
violationCount,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Cluster related failures by file and time proximity.
|
|
126
|
+
* Groups failures that share a common file or occur within a time window.
|
|
127
|
+
*/
|
|
128
|
+
export function clusterFailures(log, options = {}) {
|
|
129
|
+
const windowMs = options.windowMs ?? 60_000; // 1 minute default
|
|
130
|
+
const failures = log.query({}).filter((e) => FAILURE_KINDS.has(e.kind));
|
|
131
|
+
if (failures.length === 0)
|
|
132
|
+
return [];
|
|
133
|
+
// Group by file, then by time proximity
|
|
134
|
+
const byFile = new Map();
|
|
135
|
+
const noFile = [];
|
|
136
|
+
for (const event of failures) {
|
|
137
|
+
const file = event.context.file ?? event.payload.file;
|
|
138
|
+
if (file) {
|
|
139
|
+
const existing = byFile.get(file) ?? [];
|
|
140
|
+
existing.push(event);
|
|
141
|
+
byFile.set(file, existing);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
noFile.push(event);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const clusters = [];
|
|
148
|
+
// File-based clusters
|
|
149
|
+
for (const [file, fileEvents] of byFile) {
|
|
150
|
+
const sorted = [...fileEvents].sort((a, b) => a.timestamp - b.timestamp);
|
|
151
|
+
let clusterEvents = [sorted[0]];
|
|
152
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
153
|
+
if (sorted[i].timestamp - sorted[i - 1].timestamp <= windowMs) {
|
|
154
|
+
clusterEvents.push(sorted[i]);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
if (clusterEvents.length > 0) {
|
|
158
|
+
clusters.push({
|
|
159
|
+
id: simpleHash(`cluster:${file}:${clusterEvents[0].id}`),
|
|
160
|
+
rootEvent: clusterEvents[0],
|
|
161
|
+
events: clusterEvents,
|
|
162
|
+
commonFile: file,
|
|
163
|
+
commonKind: getMostCommonKind(clusterEvents),
|
|
164
|
+
severity: Math.min(5, clusterEvents.length),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
clusterEvents = [sorted[i]];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (clusterEvents.length > 0) {
|
|
171
|
+
clusters.push({
|
|
172
|
+
id: simpleHash(`cluster:${file}:${clusterEvents[0].id}`),
|
|
173
|
+
rootEvent: clusterEvents[0],
|
|
174
|
+
events: clusterEvents,
|
|
175
|
+
commonFile: file,
|
|
176
|
+
commonKind: getMostCommonKind(clusterEvents),
|
|
177
|
+
severity: Math.min(5, clusterEvents.length),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Time-based clusters for events without files
|
|
182
|
+
if (noFile.length > 0) {
|
|
183
|
+
const sorted = [...noFile].sort((a, b) => a.timestamp - b.timestamp);
|
|
184
|
+
let clusterEvents = [sorted[0]];
|
|
185
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
186
|
+
if (sorted[i].timestamp - sorted[i - 1].timestamp <= windowMs) {
|
|
187
|
+
clusterEvents.push(sorted[i]);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
if (clusterEvents.length > 0) {
|
|
191
|
+
clusters.push({
|
|
192
|
+
id: simpleHash(`cluster:time:${clusterEvents[0].id}`),
|
|
193
|
+
rootEvent: clusterEvents[0],
|
|
194
|
+
events: clusterEvents,
|
|
195
|
+
commonKind: getMostCommonKind(clusterEvents),
|
|
196
|
+
severity: Math.min(5, clusterEvents.length),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
clusterEvents = [sorted[i]];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (clusterEvents.length > 0) {
|
|
203
|
+
clusters.push({
|
|
204
|
+
id: simpleHash(`cluster:time:${clusterEvents[0].id}`),
|
|
205
|
+
rootEvent: clusterEvents[0],
|
|
206
|
+
events: clusterEvents,
|
|
207
|
+
commonKind: getMostCommonKind(clusterEvents),
|
|
208
|
+
severity: Math.min(5, clusterEvents.length),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return clusters.sort((a, b) => b.severity - a.severity);
|
|
213
|
+
}
|
|
214
|
+
function getMostCommonKind(events) {
|
|
215
|
+
const counts = new Map();
|
|
216
|
+
for (const e of events) {
|
|
217
|
+
counts.set(e.kind, (counts.get(e.kind) ?? 0) + 1);
|
|
218
|
+
}
|
|
219
|
+
let maxKind = events[0].kind;
|
|
220
|
+
let maxCount = 0;
|
|
221
|
+
for (const [kind, count] of counts) {
|
|
222
|
+
if (count > maxCount) {
|
|
223
|
+
maxKind = kind;
|
|
224
|
+
maxCount = count;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return maxKind;
|
|
228
|
+
}
|
|
229
|
+
// --- Encounter Mapping ---
|
|
230
|
+
const KIND_TO_ENCOUNTER = {
|
|
231
|
+
[RUNTIME_EXCEPTION]: {
|
|
232
|
+
encounterType: 'monster',
|
|
233
|
+
severity: 3,
|
|
234
|
+
name: 'Runtime Wraith',
|
|
235
|
+
},
|
|
236
|
+
[TEST_SUITE_FAILED]: {
|
|
237
|
+
encounterType: 'monster',
|
|
238
|
+
severity: 2,
|
|
239
|
+
name: 'Test Phantom',
|
|
240
|
+
},
|
|
241
|
+
[BUILD_FAILED]: {
|
|
242
|
+
encounterType: 'monster',
|
|
243
|
+
severity: 2,
|
|
244
|
+
name: 'Build Specter',
|
|
245
|
+
},
|
|
246
|
+
[DEPLOYMENT_FAILED]: {
|
|
247
|
+
encounterType: 'boss',
|
|
248
|
+
severity: 4,
|
|
249
|
+
name: 'Deploy Colossus',
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Map an execution event to a game encounter.
|
|
254
|
+
* Only failure events produce encounters.
|
|
255
|
+
* Returns null for non-failure events.
|
|
256
|
+
*/
|
|
257
|
+
export function mapToEncounter(event) {
|
|
258
|
+
const mapping = KIND_TO_ENCOUNTER[event.kind];
|
|
259
|
+
if (!mapping)
|
|
260
|
+
return null;
|
|
261
|
+
const description = event.payload.message
|
|
262
|
+
? String(event.payload.message)
|
|
263
|
+
: `${event.kind} in ${event.context.file ?? 'unknown'}`;
|
|
264
|
+
return {
|
|
265
|
+
eventId: event.id,
|
|
266
|
+
encounterType: mapping.encounterType,
|
|
267
|
+
severity: mapping.severity,
|
|
268
|
+
name: mapping.name,
|
|
269
|
+
description,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=event-projections.js.map
|