-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([parse_dependency_path/1, build_expectation_index/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", 18). ?DOC(" Extracts depends_on from an IR's SloFields.\n"). -spec get_depends_on(caffeine_lang@linker@ir:intermediate_representation(any())) -> gleam@option:option(gleam@dict:dict(caffeine_lang@linker@artifacts:dependency_relation_type(), list(binary()))). get_depends_on(Ir) -> erlang:element(8, erlang:element(5, Ir)). -file("src/caffeine_lang/analysis/dependency_validator.gleam", 409). ?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", 404). ?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", 388). ?DOC( " Collects thresholds from hard dependency targets.\n" " Skips targets that don't exist in the index.\n" ). -spec collect_hard_dep_thresholds( list(binary()), gleam@dict:dict(binary(), caffeine_lang@linker@ir:intermediate_representation(any())) ) -> 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} -> {ok, {Target_path, erlang:element(2, erlang:element(5, Target_ir))}} end end ). -file("src/caffeine_lang/analysis/dependency_validator.gleam", 339). ?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(GRB), gleam@dict:dict(binary(), caffeine_lang@linker@ir:intermediate_representation(GRB)) ) -> {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), Slo = erlang:element(5, Ir), Source_threshold = erlang:element(2, Slo), Hard_targets = case erlang:element(8, Slo) of {some, Relations} -> _pipe = gleam_stdlib:map_get(Relations, hard), gleam@result:unwrap(_pipe, []); none -> [] 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 ). -file("src/caffeine_lang/analysis/dependency_validator.gleam", 296). -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", 272). -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", 247). -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", 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", 216). ?DOC(" Builds a directed adjacency list from all IRs with depends_on.\n"). -spec build_adjacency_list( list(caffeine_lang@linker@ir:intermediate_representation(any())) ) -> gleam@dict:dict(binary(), list(binary())). build_adjacency_list(Irs) -> _pipe = Irs, _pipe@1 = gleam@list:filter_map(_pipe, fun(Ir) -> case get_depends_on(Ir) of {some, Relations} -> Path = caffeine_lang@linker@ir:ir_to_identifier(Ir), Targets = get_all_dependency_targets(Relations), {ok, {Path, Targets}}; none -> {error, nil} end end), maps:from_list(_pipe@1). -file("src/caffeine_lang/analysis/dependency_validator.gleam", 234). ?DOC(" Detects circular dependencies in the dependency graph.\n"). -spec detect_cycles( list(caffeine_lang@linker@ir:intermediate_representation(any())) ) -> {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", 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", 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", 150). -spec validate_dependency_target( binary(), binary(), gleam@dict:dict(binary(), caffeine_lang@linker@ir:intermediate_representation(any())) ) -> {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", 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", 81). -spec validate_ir_dependencies( caffeine_lang@linker@ir:intermediate_representation(GOK), gleam@dict:dict(binary(), caffeine_lang@linker@ir:intermediate_representation(GOK)) ) -> {ok, nil} | {error, caffeine_lang@errors:compilation_error()}. validate_ir_dependencies(Ir, Expectation_index) -> Depends_on = get_depends_on(Ir), gleam@bool:guard( gleam@option:is_none(Depends_on), {ok, nil}, fun() -> Self_path = caffeine_lang@linker@ir:ir_to_identifier(Ir), Relations@1 = case Depends_on of {some, Relations} -> Relations; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"caffeine_lang/analysis/dependency_validator"/utf8>>, function => <<"validate_ir_dependencies"/utf8>>, line => 92, value => _assert_fail, start => 2813, 'end' => 2859, pattern_start => 2824, pattern_end => 2846}) end, gleam@result:'try'( check_for_duplicates_per_relation(Relations@1, Self_path), fun(_) -> All_targets = get_all_dependency_targets(Relations@1), _pipe = All_targets, gleam@list:try_each( _pipe, fun(Target) -> validate_dependency_target( Target, Self_path, Expectation_index ) end ) end ) end ). -file("src/caffeine_lang/analysis/dependency_validator.gleam", 70). ?DOC(false). -spec build_expectation_index( list(caffeine_lang@linker@ir:intermediate_representation(GOE)) ) -> gleam@dict:dict(binary(), caffeine_lang@linker@ir:intermediate_representation(GOE)). 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", 32). ?DOC(false). -spec validate_dependency_relations( list(caffeine_lang@linker@ir:intermediate_representation(caffeine_lang@linker@ir:linked())) ) -> {ok, list(caffeine_lang@linker@ir:intermediate_representation(caffeine_lang@linker@ir:deps_validated()))} | {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@option:is_some(get_depends_on(Ir@1)) 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, gleam@list:map( Irs, fun caffeine_lang@linker@ir:promote/1 )} end ) end ) end ).