Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ jobs:
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Configure
run: cmake -S . -B build -D CMAKE_BUILD_TYPE=Release -D BUILD_TESTING=ON
run: >-
cmake -S . -B build
-D CMAKE_BUILD_TYPE=Release
-D BUILD_TESTING=ON
-D LOGLENS_WARNINGS_AS_ERRORS=ON

- name: Build
run: cmake --build build --config Release
Expand All @@ -48,6 +52,7 @@ jobs:
-D CMAKE_BUILD_TYPE=RelWithDebInfo
-D BUILD_TESTING=OFF
-D LOGLENS_BUILD_FUZZERS=ON
-D LOGLENS_WARNINGS_AS_ERRORS=ON

- name: Build fuzz target
run: cmake --build build-fuzz --target fuzz_parser --config RelWithDebInfo
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ jobs:
languages: c-cpp

- name: Configure
run: cmake -S . -B build -D CMAKE_BUILD_TYPE=Release -D BUILD_TESTING=ON
run: >-
cmake -S . -B build
-D CMAKE_BUILD_TYPE=Release
-D BUILD_TESTING=ON
-D LOGLENS_WARNINGS_AS_ERRORS=ON

- name: Build
run: cmake --build build --config Release
Expand Down
34 changes: 34 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,30 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

option(LOGLENS_BUILD_FUZZERS "Build Clang libFuzzer targets" OFF)
option(LOGLENS_ENABLE_WARNINGS "Enable compiler warnings for LogLens targets" ON)
option(
LOGLENS_WARNINGS_AS_ERRORS
"Treat warnings as errors for LogLens targets"
OFF
)

function(loglens_enable_warnings target)
if(NOT LOGLENS_ENABLE_WARNINGS)
return()
endif()

if(MSVC)
target_compile_options(${target} PRIVATE /W4)
if(LOGLENS_WARNINGS_AS_ERRORS)
target_compile_options(${target} PRIVATE /WX)
endif()
elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic)
if(LOGLENS_WARNINGS_AS_ERRORS)
target_compile_options(${target} PRIVATE -Werror)
endif()
endif()
endfunction()

set(LOGLENS_LIBRARY_SOURCES
src/config.cpp
Expand All @@ -35,6 +59,7 @@ set(LOGLENS_LIBRARY_SOURCES
)

add_library(loglens_lib ${LOGLENS_LIBRARY_SOURCES})
loglens_enable_warnings(loglens_lib)

target_include_directories(loglens_lib
PUBLIC
Expand All @@ -43,28 +68,34 @@ target_include_directories(loglens_lib

add_executable(loglens src/main.cpp)
target_link_libraries(loglens PRIVATE loglens_lib)
loglens_enable_warnings(loglens)
target_compile_definitions(loglens PRIVATE LOGLENS_VERSION="${LOGLENS_VERSION}")

include(CTest)
if(BUILD_TESTING)
add_executable(test_parser tests/test_parser.cpp)
target_link_libraries(test_parser PRIVATE loglens_lib)
loglens_enable_warnings(test_parser)
add_test(NAME parser COMMAND test_parser)

add_executable(test_parser_properties tests/test_parser_properties.cpp)
target_link_libraries(test_parser_properties PRIVATE loglens_lib)
loglens_enable_warnings(test_parser_properties)
add_test(NAME parser_properties COMMAND test_parser_properties)

add_executable(test_detector tests/test_detector.cpp)
target_link_libraries(test_detector PRIVATE loglens_lib)
loglens_enable_warnings(test_detector)
add_test(NAME detector COMMAND test_detector)

add_executable(test_report tests/test_report.cpp)
target_link_libraries(test_report PRIVATE loglens_lib)
loglens_enable_warnings(test_report)
add_test(NAME report COMMAND test_report)

add_executable(test_cli tests/test_cli.cpp)
target_link_libraries(test_cli PRIVATE loglens_lib)
loglens_enable_warnings(test_cli)
add_test(
NAME cli
COMMAND test_cli
Expand All @@ -75,6 +106,7 @@ if(BUILD_TESTING)
)

add_executable(test_report_contracts tests/test_report_contracts.cpp)
loglens_enable_warnings(test_report_contracts)
add_test(
NAME report_contracts
COMMAND test_report_contracts
Expand All @@ -90,6 +122,7 @@ if(LOGLENS_BUILD_FUZZERS)

add_library(loglens_fuzz_lib STATIC ${LOGLENS_LIBRARY_SOURCES})
target_include_directories(loglens_fuzz_lib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
loglens_enable_warnings(loglens_fuzz_lib)
target_compile_options(
loglens_fuzz_lib
PRIVATE
Expand All @@ -99,6 +132,7 @@ if(LOGLENS_BUILD_FUZZERS)

add_executable(fuzz_parser fuzz/parser_fuzz.cpp)
target_link_libraries(fuzz_parser PRIVATE loglens_fuzz_lib)
loglens_enable_warnings(fuzz_parser)
target_compile_options(
fuzz_parser
PRIVATE
Expand Down
3 changes: 2 additions & 1 deletion CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"binaryDir": "${sourceDir}/build/ci-release",
"cacheVariables": {
"BUILD_TESTING": "ON",
"CMAKE_BUILD_TYPE": "Release"
"CMAKE_BUILD_TYPE": "Release",
"LOGLENS_WARNINGS_AS_ERRORS": "ON"
}
}
],
Expand Down
118 changes: 118 additions & 0 deletions docs/adr/0001-episode-semantics-boundaries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# ADR 0001: Episode Semantics Boundaries for v0.7 Research

- Status: Proposed
- Date: 2026-08-08
- Owner: stacknil

## Context

LogLens v0.6 reports repeated findings as detector episodes. The public v0.6
contract names four policy points: threshold crossing, maximal window,
non-overlapping windows, and cooldown merge. An episode is a reporting unit,
not an incident boundary.

The current implementation groups each rule subject by source IP or username,
sorts its signals by time, cuts an activity segment when an adjacent event gap
is greater than the rule window, and selects one best sliding window inside
each segment. The selected window must meet the rule threshold before a finding
is emitted. See [`src/detector.cpp`](../../src/detector.cpp) and the v0.6
[`Episode Policy`](../release-v0.6.0.md#episode-policy).

This baseline is deterministic and works for clearly separated bursts. It has
an important research boundary: a continuous low-density background stream can
keep two dense peaks in the same activity segment. The best-window selection
then emits one peak and can hide the fact that a second dense peak exists.

For example, with a ten-minute rule window and five-event threshold, sparse
background events every nine minutes can connect two five-event bursts. There
is no adjacent gap large enough to cut the segment, even if the two bursts are
far apart in aggregate time. Under the current algorithm, the segment is
eligible for one best-window selection; it is not eligible for two peak-based
episodes merely because its internal density is bimodal.

This is a v0.7 research boundary, not a v0.6 release blocker. The v0.6
algorithm and report contract remain unchanged by this ADR.

## Decision

Keep the v0.6 episode algorithm unchanged and make the following semantics
explicit for v0.7 research:

1. **Threshold crossing** is an eligibility event. The first window that meets
the threshold proves that a candidate can emit a finding; it does not by
itself define the episode start or the final reported window.
2. **Maximal window** means the selected highest-signal window under the rule's
objective. In v0.6 this is a best-count sliding window (or the
distinct-username objective for multi-user probing), not the longest possible
time span. Tie-breaking and whether a candidate may be extended without
increasing its score are v0.7 research questions.
3. **Non-overlapping episode** is currently enforced by segment construction
and one selection per segment. It does not yet describe a global candidate
ranking problem where two windows compete for shared events. v0.7 must state
whether exclusion is by event IDs, time intervals, or a rule-specific signal
budget.
4. **Cooldown merge** currently means that adjacent signals with a gap less
than or equal to the rule window remain in one candidate segment. A gap
larger than the rule window starts another segment. This is an adjacency
rule, not a density-aware model of background activity.
5. **Continuous background with two dense peaks** is a required research fixture
category. The fixture must preserve the sparse bridge events, show both
dense peaks, and record that the v0.6 baseline may return one finding. A
candidate v0.7 algorithm may split the peaks only with an explicit, tested
separation policy.

No detector implementation change is part of this ADR. The first v0.7 change
should be a fixture and oracle that makes the baseline behavior measurable
before a new segmentation algorithm is selected.

## Research fixture contract

The fixture should use sanitized synthetic events and a fixed rule configuration
that makes each boundary observable. At minimum it should cover:

| Case | Required observation |
| --- | --- |
| Isolated dense bursts | Two bursts separated by a gap greater than the rule window produce two baseline episodes. |
| Continuous bridge | Two dense bursts connected by background gaps at or below the rule window remain one baseline segment. |
| Threshold edge | A candidate exactly at threshold is eligible; one event below threshold is not. |
| Maximal-window tie | Equal-score windows have a documented deterministic tie-break. |
| Shared evidence | Overlapping candidate windows make the non-overlap unit explicit. |
| Cooldown boundary | A gap exactly at the rule window and one second beyond it test the inclusive boundary. |
| Bimodal background | Two dense peaks are surrounded by lower-rate background, with expected baseline output recorded separately from the research candidate output. |

Each case should record raw event IDs, timestamps, rule window, threshold,
candidate windows, selected windows, episode indexes, and the reason an event
was included or excluded. The fixture must not claim compromise, intent,
attribution, or an incident boundary.

## Alternatives considered

- **Keep adjacent-gap segmentation as the final model** - simple and
deterministic, but it cannot distinguish a continuous background bridge from
a single coherent burst.
- **Split every local density peak** - can expose bimodal activity, but needs
explicit peak prominence, minimum separation, and noise-handling parameters;
it may over-split one operational burst.
- **Use a density-aware cooldown or bridge budget** - directly targets the
continuous-background case, but introduces additional policy knobs and needs
fixture-backed calibration before it can become a stable contract.
- **Emit all threshold-crossing windows** - preserves more evidence, but creates
overlapping findings and makes `finding_id`, episode identity, and downstream
deduplication ambiguous.

## Consequences

- v0.6 remains stable and does not acquire an untested density model.
- v0.7 gets a concrete research target instead of an ambiguous request to
"improve episodes."
- A future algorithm must be compared with the current baseline on the same
fixture, including cases where the baseline behavior is intentionally
preserved.
- The report contract may need an explicit candidate/selection distinction if
v0.7 emits multiple peak candidates from one continuous segment.

## References

- [`v0.6 Episode Policy`](../release-v0.6.0.md#episode-policy)
- [`Detector implementation`](../../src/detector.cpp)
- [`Detector tests`](../../tests/test_detector.cpp)
4 changes: 4 additions & 0 deletions docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ The CLI in `src/main.cpp` wires those layers together:
- write `report.md` and `report.json`

This keeps the MVP easy to test and extend without introducing heavy dependencies or unnecessary abstractions.

## Architecture decisions

- [ADR 0001: Episode Semantics Boundaries for v0.7 Research](adr/0001-episode-semantics-boundaries.md)
10 changes: 10 additions & 0 deletions docs/dev-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ cmake --build --preset ci-release
ctest --preset ci-release
```

Normal LogLens targets enable compiler warnings by default. GCC/Clang builds
use `-Wall -Wextra -Wpedantic`; MSVC builds use `/W4`. Warnings are scoped to
LogLens-owned targets rather than applied globally to a consumer's directory.

Warnings-as-errors are disabled for ordinary local builds. CI enables
`LOGLENS_WARNINGS_AS_ERRORS=ON`, which adds `-Werror` or `/WX` only to those
LogLens-owned targets. A local toolchain can opt out of the warning flags with
`-D LOGLENS_ENABLE_WARNINGS=OFF` when compiler or third-party integration
differences require it.

## Manual Fallback

If you do not want to use presets, or if your local CMake is 3.20 but not 3.21+, the equivalent manual flow is:
Expand Down
Loading