-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", 64). ?DOC(false). -spec build_expectation_index( list(caffeine_lang@linker@ir:intermediate_representation(HEA)) ) -> gleam@dict:dict(binary(), caffeine_lang@linker@ir:intermediate_representation(HEA)). 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", 109). -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", 129). -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", 117). -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", 188). ?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", 200). ?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", 152). -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", 75). -spec validate_ir_dependencies( caffeine_lang@linker@ir:intermediate_representation(HEG), gleam@dict:dict(binary(), caffeine_lang@linker@ir:intermediate_representation(HEG)) ) -> {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( <> ) ) 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", 218). ?DOC(" Builds a directed adjacency list from all IRs with DependencyRelations.\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( _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", 401). ?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(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} -> 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", 426). ?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", 431). ?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", 342). ?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(HGX), gleam@dict:dict(binary(), caffeine_lang@linker@ir:intermediate_representation(HGX)) ) -> {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( <> )} 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", 299). -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", 275). -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", 250). -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", 237). ?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", 23). ?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@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, gleam@list:map( Irs, fun caffeine_lang@linker@ir:promote/1 )} end ) end ) end ).