Current section

Files

Jump to
caffeine_lang src caffeine_lang@analysis@dependency_validator.erl
Raw

src/caffeine_lang@analysis@dependency_validator.erl

-module(caffeine_lang@analysis@dependency_validator).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/caffeine_lang/analysis/dependency_validator.gleam").
-export([build_expectation_index/1, parse_dependency_path/1, validate_dependency_relations/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 62).
?DOC(false).
-spec build_expectation_index(
list(caffeine_lang@linker@ir:intermediate_representation())
) -> gleam@dict:dict(binary(), caffeine_lang@linker@ir:intermediate_representation()).
build_expectation_index(Irs) ->
_pipe = Irs,
_pipe@1 = gleam@list:map(
_pipe,
fun(Ir) ->
Path = caffeine_lang@linker@ir:ir_to_identifier(Ir),
{Path, Ir}
end
),
maps:from_list(_pipe@1).
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 107).
-spec get_all_dependency_targets(
gleam@dict:dict(caffeine_lang@linker@artifacts:dependency_relation_type(), list(binary()))
) -> list(binary()).
get_all_dependency_targets(Relations) ->
_pipe = Relations,
_pipe@1 = maps:values(_pipe),
lists:append(_pipe@1).
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 127).
-spec do_check_for_duplicates(list(binary()), gleam@set:set(binary()), binary()) -> {ok,
nil} |
{error, caffeine_lang@errors:compilation_error()}.
do_check_for_duplicates(Targets, Seen, Self_path) ->
case Targets of
[] ->
{ok, nil};
[Target | Rest] ->
gleam@bool:guard(
gleam@set:contains(Seen, Target),
{error,
caffeine_lang@errors:semantic_analysis_dependency_validation_error(
<<<<<<<<"Duplicate dependency reference '"/utf8,
Target/binary>>/binary,
"' in '"/utf8>>/binary,
Self_path/binary>>/binary,
"'"/utf8>>
)},
fun() ->
do_check_for_duplicates(
Rest,
gleam@set:insert(Seen, Target),
Self_path
)
end
)
end.
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 115).
-spec check_for_duplicates_per_relation(
gleam@dict:dict(caffeine_lang@linker@artifacts:dependency_relation_type(), list(binary())),
binary()
) -> {ok, nil} | {error, caffeine_lang@errors:compilation_error()}.
check_for_duplicates_per_relation(Relations, Self_path) ->
_pipe = Relations,
_pipe@1 = maps:to_list(_pipe),
gleam@list:try_each(
_pipe@1,
fun(Pair) ->
{_, Targets} = Pair,
do_check_for_duplicates(Targets, gleam@set:new(), Self_path)
end
).
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 186).
?DOC(false).
-spec parse_dependency_path(binary()) -> {ok,
{binary(), binary(), binary(), binary()}} |
{error, nil}.
parse_dependency_path(Path) ->
case gleam@string:split(Path, <<"."/utf8>>) of
[Org, Team, Service, Name] when (((Org =/= <<""/utf8>>) andalso (Team =/= <<""/utf8>>)) andalso (Service =/= <<""/utf8>>)) andalso (Name =/= <<""/utf8>>) ->
{ok, {Org, Team, Service, Name}};
_ ->
{error, nil}
end.
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 198).
?DOC(" Build a dependency reference error with a consistent message format.\n").
-spec dependency_ref_error(binary(), binary(), binary()) -> caffeine_lang@errors:compilation_error().
dependency_ref_error(Target, Self_path, Reason) ->
caffeine_lang@errors:semantic_analysis_dependency_validation_error(
<<<<<<<<<<"Invalid dependency reference '"/utf8, Target/binary>>/binary,
"' in '"/utf8>>/binary,
Self_path/binary>>/binary,
"': "/utf8>>/binary,
Reason/binary>>
).
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 150).
-spec validate_dependency_target(
binary(),
binary(),
gleam@dict:dict(binary(), caffeine_lang@linker@ir:intermediate_representation())
) -> {ok, nil} | {error, caffeine_lang@errors:compilation_error()}.
validate_dependency_target(Target, Self_path, Expectation_index) ->
case parse_dependency_path(Target) of
{error, nil} ->
{error,
dependency_ref_error(
Target,
Self_path,
<<"expected format 'org.team.service.name'"/utf8>>
)};
{ok, _} ->
gleam@bool:guard(
Target =:= Self_path,
{error,
dependency_ref_error(
Target,
Self_path,
<<"self-reference not allowed"/utf8>>
)},
fun() -> case gleam_stdlib:map_get(Expectation_index, Target) of
{ok, _} ->
{ok, nil};
{error, nil} ->
{error,
dependency_ref_error(
Target,
Self_path,
<<"target does not exist"/utf8>>
)}
end end
)
end.
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 73).
-spec validate_ir_dependencies(
caffeine_lang@linker@ir:intermediate_representation(),
gleam@dict:dict(binary(), caffeine_lang@linker@ir:intermediate_representation())
) -> {ok, nil} | {error, caffeine_lang@errors:compilation_error()}.
validate_ir_dependencies(Ir, Expectation_index) ->
gleam@bool:guard(
not gleam@list:contains(erlang:element(4, Ir), dependency_relations),
{ok, nil},
fun() ->
Self_path = caffeine_lang@linker@ir:ir_to_identifier(Ir),
gleam@result:'try'(
begin
_pipe = caffeine_lang@linker@ir:get_dependency_fields(
erlang:element(6, Ir)
),
gleam@option:to_result(
_pipe,
caffeine_lang@errors:semantic_analysis_dependency_validation_error(
<<Self_path/binary,
" - missing dependency artifact data"/utf8>>
)
)
end,
fun(Dep) ->
Relations = erlang:element(2, Dep),
gleam@result:'try'(
check_for_duplicates_per_relation(Relations, Self_path),
fun(_) ->
All_targets = get_all_dependency_targets(Relations),
_pipe@1 = All_targets,
gleam@list:try_each(
_pipe@1,
fun(Target) ->
validate_dependency_target(
Target,
Self_path,
Expectation_index
)
end
)
end
)
end
)
end
).
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 216).
?DOC(" Builds a directed adjacency list from all IRs with DependencyRelations.\n").
-spec build_adjacency_list(
list(caffeine_lang@linker@ir:intermediate_representation())
) -> gleam@dict:dict(binary(), list(binary())).
build_adjacency_list(Irs) ->
_pipe = Irs,
_pipe@1 = gleam@list:filter(
_pipe,
fun(Ir) ->
gleam@list:contains(erlang:element(4, Ir), dependency_relations)
end
),
_pipe@2 = gleam@list:filter_map(
_pipe@1,
fun(Ir@1) ->
case caffeine_lang@linker@ir:get_dependency_fields(
erlang:element(6, Ir@1)
) of
{some, Dep} ->
Path = caffeine_lang@linker@ir:ir_to_identifier(Ir@1),
Targets = get_all_dependency_targets(erlang:element(2, Dep)),
{ok, {Path, Targets}};
none ->
{error, nil}
end
end
),
maps:from_list(_pipe@2).
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 399).
?DOC(
" Collects thresholds from hard dependency targets that have SLO artifacts.\n"
" Skips targets that don't exist in the index or don't have SLO artifacts.\n"
" Note: targets with SLO in artifact_refs but missing SLO data are skipped\n"
" because the linker guarantees consistency between artifact_refs and artifact_data.\n"
).
-spec collect_hard_dep_thresholds(
list(binary()),
gleam@dict:dict(binary(), caffeine_lang@linker@ir:intermediate_representation())
) -> list({binary(), float()}).
collect_hard_dep_thresholds(Targets, Expectation_index) ->
_pipe = Targets,
gleam@list:filter_map(
_pipe,
fun(Target_path) ->
case gleam_stdlib:map_get(Expectation_index, Target_path) of
{error, nil} ->
{error, nil};
{ok, Target_ir} ->
gleam@bool:guard(
not gleam@list:contains(
erlang:element(4, Target_ir),
s_l_o
),
{error, nil},
fun() ->
case caffeine_lang@linker@ir:get_slo_fields(
erlang:element(6, Target_ir)
) of
none ->
{error, nil};
{some, Slo} ->
{ok, {Target_path, erlang:element(2, Slo)}}
end
end
)
end
end
).
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 424).
?DOC(
" Computes the composite availability ceiling from a list of thresholds.\n"
" Each threshold is a percentage (e.g. 99.99). The composite ceiling is\n"
" the product of individual availabilities.\n"
).
-spec compute_composite_ceiling(list(float())) -> float().
compute_composite_ceiling(Thresholds) ->
gleam@list:fold(Thresholds, 1.0, fun(Acc, T) -> Acc * (T / 100.0) end) * 100.0.
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 429).
?DOC(" Rounds a float to 4 decimal places for cleaner display.\n").
-spec round_to_4(float()) -> float().
round_to_4(F) ->
erlang:float(erlang:round(F * 10000.0)) / 10000.0.
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 340).
?DOC(
" Validates hard dependency thresholds for a single IR using composite ceiling.\n"
" The composite ceiling is the product of all hard dependency thresholds,\n"
" representing the maximum achievable availability given those dependencies.\n"
).
-spec validate_single_ir_hard_thresholds(
caffeine_lang@linker@ir:intermediate_representation(),
gleam@dict:dict(binary(), caffeine_lang@linker@ir:intermediate_representation())
) -> {ok, nil} | {error, caffeine_lang@errors:compilation_error()}.
validate_single_ir_hard_thresholds(Ir, Expectation_index) ->
Self_path = caffeine_lang@linker@ir:ir_to_identifier(Ir),
gleam@result:'try'(
case {caffeine_lang@linker@ir:get_slo_fields(erlang:element(6, Ir)),
caffeine_lang@linker@ir:get_dependency_fields(erlang:element(6, Ir))} of
{{some, Slo}, {some, Dep}} ->
{ok, {Slo, Dep}};
{_, _} ->
{error,
caffeine_lang@errors:semantic_analysis_dependency_validation_error(
<<Self_path/binary,
" - missing SLO or dependency artifact data"/utf8>>
)}
end,
fun(_use0) ->
{Slo@1, Dep@1} = _use0,
Source_threshold = erlang:element(2, Slo@1),
Hard_targets = begin
_pipe = gleam_stdlib:map_get(erlang:element(2, Dep@1), hard),
gleam@result:unwrap(_pipe, [])
end,
Dep_thresholds = collect_hard_dep_thresholds(
Hard_targets,
Expectation_index
),
gleam@bool:guard(
gleam@list:is_empty(Dep_thresholds),
{ok, nil},
fun() ->
Composite_ceiling = compute_composite_ceiling(
gleam@list:map(
Dep_thresholds,
fun(Pair) -> erlang:element(2, Pair) end
)
),
Display_ceiling = round_to_4(Composite_ceiling),
case gleam@float:compare(
Source_threshold,
Composite_ceiling
) of
gt ->
Deps_description = begin
_pipe@1 = Dep_thresholds,
_pipe@2 = gleam@list:map(
_pipe@1,
fun(Pair@1) ->
<<<<<<<<"'"/utf8,
(erlang:element(
1,
Pair@1
))/binary>>/binary,
"' ("/utf8>>/binary,
(gleam_stdlib:float_to_string(
erlang:element(2, Pair@1)
))/binary>>/binary,
")"/utf8>>
end
),
gleam@string:join(_pipe@2, <<", "/utf8>>)
end,
{error,
caffeine_lang@errors:semantic_analysis_dependency_validation_error(
<<<<<<<<<<<<<<"Composite hard dependency threshold violation: '"/utf8,
Self_path/binary>>/binary,
"' (threshold: "/utf8>>/binary,
(gleam_stdlib:float_to_string(
Source_threshold
))/binary>>/binary,
") exceeds the composite availability ceiling of "/utf8>>/binary,
(gleam_stdlib:float_to_string(
Display_ceiling
))/binary>>/binary,
" from its hard dependencies: "/utf8>>/binary,
Deps_description/binary>>
)};
_ ->
{ok, nil}
end
end
)
end
).
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 297).
-spec explore_neighbors(
list(binary()),
gleam@dict:dict(binary(), list(binary())),
gleam@set:set(binary()),
gleam@set:set(binary()),
list(binary())
) -> {ok, {gleam@set:set(binary()), gleam@set:set(binary())}} |
{error, caffeine_lang@errors:compilation_error()}.
explore_neighbors(Neighbors, Adjacency, Visited, In_progress, Path) ->
case Neighbors of
[] ->
{ok, {Visited, In_progress}};
[Neighbor | Rest] ->
gleam@bool:guard(
gleam@set:contains(In_progress, Neighbor),
{error,
caffeine_lang@errors:semantic_analysis_dependency_validation_error(
<<<<<<"Circular dependency detected: "/utf8,
(gleam@string:join(
lists:reverse(Path),
<<" -> "/utf8>>
))/binary>>/binary,
" -> "/utf8>>/binary,
Neighbor/binary>>
)},
fun() ->
gleam@bool:guard(
gleam@set:contains(Visited, Neighbor),
explore_neighbors(
Rest,
Adjacency,
Visited,
In_progress,
Path
),
fun() ->
gleam@result:'try'(
explore_node(
Neighbor,
Adjacency,
Visited,
In_progress,
[Neighbor | Path]
),
fun(_use0) ->
{Visited@1, In_progress@1} = _use0,
explore_neighbors(
Rest,
Adjacency,
Visited@1,
In_progress@1,
Path
)
end
)
end
)
end
)
end.
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 273).
-spec explore_node(
binary(),
gleam@dict:dict(binary(), list(binary())),
gleam@set:set(binary()),
gleam@set:set(binary()),
list(binary())
) -> {ok, {gleam@set:set(binary()), gleam@set:set(binary())}} |
{error, caffeine_lang@errors:compilation_error()}.
explore_node(Node, Adjacency, Visited, In_progress, Path) ->
In_progress@1 = gleam@set:insert(In_progress, Node),
Neighbors = begin
_pipe = gleam_stdlib:map_get(Adjacency, Node),
gleam@result:unwrap(_pipe, [])
end,
gleam@result:'try'(
explore_neighbors(Neighbors, Adjacency, Visited, In_progress@1, Path),
fun(_use0) ->
{Visited@1, In_progress@2} = _use0,
Visited@2 = gleam@set:insert(Visited@1, Node),
In_progress@3 = gleam@set:delete(In_progress@2, Node),
{ok, {Visited@2, In_progress@3}}
end
).
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 248).
-spec detect_cycles_loop(
list(binary()),
gleam@dict:dict(binary(), list(binary())),
gleam@set:set(binary()),
gleam@set:set(binary())
) -> {ok, gleam@set:set(binary())} |
{error, caffeine_lang@errors:compilation_error()}.
detect_cycles_loop(Nodes, Adjacency, Visited, In_progress) ->
case Nodes of
[] ->
{ok, Visited};
[Node | Rest] ->
gleam@bool:guard(
gleam@set:contains(Visited, Node),
detect_cycles_loop(Rest, Adjacency, Visited, In_progress),
fun() ->
gleam@result:'try'(
explore_node(
Node,
Adjacency,
Visited,
In_progress,
[Node]
),
fun(_use0) ->
{Visited@1, In_progress@1} = _use0,
detect_cycles_loop(
Rest,
Adjacency,
Visited@1,
In_progress@1
)
end
)
end
)
end.
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 235).
?DOC(" Detects circular dependencies in the dependency graph.\n").
-spec detect_cycles(list(caffeine_lang@linker@ir:intermediate_representation())) -> {ok,
nil} |
{error, caffeine_lang@errors:compilation_error()}.
detect_cycles(Irs) ->
Adjacency = build_adjacency_list(Irs),
Nodes = begin
_pipe = Adjacency,
_pipe@1 = maps:keys(_pipe),
gleam@list:sort(_pipe@1, fun gleam@string:compare/2)
end,
_pipe@2 = detect_cycles_loop(
Nodes,
Adjacency,
gleam@set:new(),
gleam@set:new()
),
gleam@result:map(_pipe@2, fun(_) -> nil end).
-file("src/caffeine_lang/analysis/dependency_validator.gleam", 25).
?DOC(false).
-spec validate_dependency_relations(
list(caffeine_lang@linker@ir:intermediate_representation())
) -> {ok, list(caffeine_lang@linker@ir:intermediate_representation())} |
{error, caffeine_lang@errors:compilation_error()}.
validate_dependency_relations(Irs) ->
Expectation_index = build_expectation_index(Irs),
gleam@result:'try'(
begin
_pipe = Irs,
_pipe@1 = gleam@list:map(
_pipe,
fun(Ir) -> validate_ir_dependencies(Ir, Expectation_index) end
),
_pipe@2 = caffeine_lang@errors:from_results(_pipe@1),
gleam@result:map(_pipe@2, fun(_) -> nil end)
end,
fun(_) ->
gleam@result:'try'(
detect_cycles(Irs),
fun(_) ->
gleam@result:'try'(
begin
_pipe@3 = Irs,
_pipe@4 = gleam@list:filter(
_pipe@3,
fun(Ir@1) ->
gleam@list:contains(
erlang:element(4, Ir@1),
dependency_relations
)
andalso gleam@list:contains(
erlang:element(4, Ir@1),
s_l_o
)
end
),
_pipe@5 = gleam@list:map(
_pipe@4,
fun(Ir@2) ->
validate_single_ir_hard_thresholds(
Ir@2,
Expectation_index
)
end
),
_pipe@6 = caffeine_lang@errors:from_results(_pipe@5),
gleam@result:map(_pipe@6, fun(_) -> nil end)
end,
fun(_) -> {ok, Irs} end
)
end
)
end
).