-module(lightspeed@ops@migration_harness). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/lightspeed/ops/migration_harness.gleam"). -export([run_scenario/1, run_matrix/0, scenario_label/1, pass_fail_label/1, signature/1, scenario/1, outcomes/1, failed_scenarios/1, report_signature/1]). -export_type([scenario/0, scenario_outcome/0, report/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(" Deterministic M19 harness for scoped data access and migration plans.\n"). -type scenario() :: small_reference_migration | medium_reference_migration | authz_scoped_access | rollback_mixed_runtime. -type scenario_outcome() :: {scenario_outcome, scenario(), boolean(), binary()}. -type report() :: {report, list(scenario_outcome()), integer()}. -file("src/lightspeed/ops/migration_harness.gleam", 257). -spec count_failed(list(scenario_outcome())) -> integer(). count_failed(Outcomes) -> case Outcomes of [] -> 0; [Outcome | Rest] -> case erlang:element(3, Outcome) of true -> count_failed(Rest); false -> 1 + count_failed(Rest) end end. -file("src/lightspeed/ops/migration_harness.gleam", 268). -spec join_with(binary(), list(binary())) -> binary(). join_with(Separator, Values) -> case Values of [] -> <<""/utf8>>; [Value] -> Value; [Value@1 | Rest] -> <<<>/binary, (join_with(Separator, Rest))/binary>> end. -file("src/lightspeed/ops/migration_harness.gleam", 243). -spec contains_step( list(lightspeed@data@migration:step()), lightspeed@data@migration:step() ) -> boolean(). contains_step(Steps, Expected) -> case Steps of [] -> false; [Step | Rest] -> case Step =:= Expected of true -> true; false -> contains_step(Rest, Expected) end end. -file("src/lightspeed/ops/migration_harness.gleam", 219). -spec evaluate_rollback_mixed_runtime() -> scenario_outcome(). evaluate_rollback_mixed_runtime() -> Plan = lightspeed@data@migration:medium_reference_plan(), Rollback = lightspeed@data@migration:rollback_steps(Plan), Adapters = lightspeed@data@migration:adapters(Plan), Adapter_labels = gleam@list:map( Adapters, fun lightspeed@data@repository:adapter_label/1 ), Rollback_labels = gleam@list:map( Rollback, fun lightspeed@data@migration:step_label/1 ), Passed = ((lightspeed@data@migration:mode_label(erlang:element(4, Plan)) =:= <<"mixed_runtime"/utf8>>) andalso contains_step(Rollback, rollback_to_legacy)) andalso (erlang:length(Adapters) >= 2), {scenario_outcome, rollback_mixed_runtime, Passed, <<<<<<<<<<"mode="/utf8, (lightspeed@data@migration:mode_label( erlang:element(4, Plan) ))/binary>>/binary, "|adapters="/utf8>>/binary, (join_with(<<","/utf8>>, Adapter_labels))/binary>>/binary, "|rollback="/utf8>>/binary, (join_with(<<","/utf8>>, Rollback_labels))/binary>>}. -file("src/lightspeed/ops/migration_harness.gleam", 123). -spec evaluate_authz_scoped_access() -> scenario_outcome(). evaluate_authz_scoped_access() -> Repo = lightspeed@data@repository:seed( {elixir_bridge, <<"MyApp.Repo"/utf8>>}, [{record, <<"r-1"/utf8>>, <<"t-1"/utf8>>, <<"u-1"/utf8>>, <<"invoice"/utf8>>, <<"100"/utf8>>}, {record, <<"r-2"/utf8>>, <<"t-2"/utf8>>, <<"u-2"/utf8>>, <<"invoice"/utf8>>, <<"200"/utf8>>}] ), Viewer = lightspeed@data@repository:tenant_scope( <<"actor-viewer"/utf8>>, <<"t-1"/utf8>>, viewer ), Editor = lightspeed@data@repository:tenant_scope( <<"actor-editor"/utf8>>, <<"t-1"/utf8>>, editor ), System = lightspeed@data@repository:system_scope(<<"actor-system"/utf8>>), Cross_tenant_read = lightspeed@data@repository:fetch_by_id( Repo, Viewer, <<"r-2"/utf8>> ), Own_tenant_read = lightspeed@data@repository:fetch_by_id( Repo, Viewer, <<"r-1"/utf8>> ), Viewer_write_denied = lightspeed@data@repository:upsert( Repo, Viewer, {record, <<"r-3"/utf8>>, <<"t-1"/utf8>>, <<"u-1"/utf8>>, <<"invoice"/utf8>>, <<"300"/utf8>>} ), Editor_write_allowed = lightspeed@data@repository:upsert( Repo, Editor, {record, <<"r-4"/utf8>>, <<"t-1"/utf8>>, <<"u-1"/utf8>>, <<"invoice"/utf8>>, <<"400"/utf8>>} ), System_cross_tenant_delete = lightspeed@data@repository:delete_by_id( Repo, System, <<"r-2"/utf8>> ), Cross_tenant_denied = case Cross_tenant_read of {error, Error} -> lightspeed@data@repository:error_label(Error); {ok, _} -> <<"unexpected_ok"/utf8>> end, Own_tenant_result = case Own_tenant_read of {ok, Record} -> <<"ok:"/utf8, (erlang:element(2, Record))/binary>>; {error, Error@1} -> lightspeed@data@repository:error_label(Error@1) end, Viewer_write_result = case Viewer_write_denied of {error, Error@2} -> lightspeed@data@repository:error_label(Error@2); {ok, _} -> <<"unexpected_ok"/utf8>> end, Editor_write_result = case Editor_write_allowed of {ok, Updated} -> <<"ok:"/utf8, (erlang:integer_to_binary( erlang:length(lightspeed@data@repository:all(Updated)) ))/binary>>; {error, Error@3} -> lightspeed@data@repository:error_label(Error@3) end, System_delete_result = case System_cross_tenant_delete of {ok, Updated@1} -> <<"ok:"/utf8, (erlang:integer_to_binary( erlang:length(lightspeed@data@repository:all(Updated@1)) ))/binary>>; {error, Error@4} -> lightspeed@data@repository:error_label(Error@4) end, Passed = ((((Cross_tenant_denied =:= <<"forbidden:actor-viewer:read:t-2"/utf8>>) andalso (Own_tenant_result =:= <<"ok:r-1"/utf8>>)) andalso (Viewer_write_result =:= <<"forbidden:actor-viewer:write:t-1"/utf8>>)) andalso (Editor_write_result =:= <<"ok:3"/utf8>>)) andalso (System_delete_result =:= <<"ok:1"/utf8>>), {scenario_outcome, authz_scoped_access, Passed, <<<<<<<<<<<<<<<<<<<<<<"adapter="/utf8, (lightspeed@data@repository:adapter_label( lightspeed@data@repository:adapter( Repo ) ))/binary>>/binary, "|cross_tenant="/utf8>>/binary, Cross_tenant_denied/binary>>/binary, "|own_tenant="/utf8>>/binary, Own_tenant_result/binary>>/binary, "|viewer_write="/utf8>>/binary, Viewer_write_result/binary>>/binary, "|editor_write="/utf8>>/binary, Editor_write_result/binary>>/binary, "|system_delete="/utf8>>/binary, System_delete_result/binary>>}. -file("src/lightspeed/ops/migration_harness.gleam", 112). -spec evaluate_medium_reference_migration() -> scenario_outcome(). evaluate_medium_reference_migration() -> Plan = lightspeed@data@migration:medium_reference_plan(), Valid = lightspeed@data@migration:valid(Plan), {scenario_outcome, medium_reference_migration, Valid, lightspeed@data@migration:signature(Plan)}. -file("src/lightspeed/ops/migration_harness.gleam", 101). -spec evaluate_small_reference_migration() -> scenario_outcome(). evaluate_small_reference_migration() -> Plan = lightspeed@data@migration:small_reference_plan(), Valid = lightspeed@data@migration:valid(Plan), {scenario_outcome, small_reference_migration, Valid, lightspeed@data@migration:signature(Plan)}. -file("src/lightspeed/ops/migration_harness.gleam", 40). ?DOC(" Run one scenario.\n"). -spec run_scenario(scenario()) -> scenario_outcome(). run_scenario(Scenario) -> case Scenario of small_reference_migration -> evaluate_small_reference_migration(); medium_reference_migration -> evaluate_medium_reference_migration(); authz_scoped_access -> evaluate_authz_scoped_access(); rollback_mixed_runtime -> evaluate_rollback_mixed_runtime() end. -file("src/lightspeed/ops/migration_harness.gleam", 27). ?DOC(" Run all M19 scenarios.\n"). -spec run_matrix() -> report(). run_matrix() -> Scenarios = [small_reference_migration, medium_reference_migration, authz_scoped_access, rollback_mixed_runtime], Outcomes = gleam@list:map(Scenarios, fun run_scenario/1), {report, Outcomes, count_failed(Outcomes)}. -file("src/lightspeed/ops/migration_harness.gleam", 50). ?DOC(" Scenario label.\n"). -spec scenario_label(scenario()) -> binary(). scenario_label(Scenario) -> case Scenario of small_reference_migration -> <<"small_reference_migration"/utf8>>; medium_reference_migration -> <<"medium_reference_migration"/utf8>>; authz_scoped_access -> <<"authz_scoped_access"/utf8>>; rollback_mixed_runtime -> <<"rollback_mixed_runtime"/utf8>> end. -file("src/lightspeed/ops/migration_harness.gleam", 60). ?DOC(" Stable pass/fail label.\n"). -spec pass_fail_label(scenario_outcome()) -> binary(). pass_fail_label(Outcome) -> case erlang:element(3, Outcome) of true -> <<"pass"/utf8>>; false -> <<"fail"/utf8>> end. -file("src/lightspeed/ops/migration_harness.gleam", 68). ?DOC(" Scenario signature.\n"). -spec signature(scenario_outcome()) -> binary(). signature(Outcome) -> erlang:element(4, Outcome). -file("src/lightspeed/ops/migration_harness.gleam", 73). ?DOC(" Scenario accessor.\n"). -spec scenario(scenario_outcome()) -> scenario(). scenario(Outcome) -> erlang:element(2, Outcome). -file("src/lightspeed/ops/migration_harness.gleam", 78). ?DOC(" Report outcomes.\n"). -spec outcomes(report()) -> list(scenario_outcome()). outcomes(Report) -> erlang:element(2, Report). -file("src/lightspeed/ops/migration_harness.gleam", 83). ?DOC(" Failed scenario count.\n"). -spec failed_scenarios(report()) -> integer(). failed_scenarios(Report) -> erlang:element(3, Report). -file("src/lightspeed/ops/migration_harness.gleam", 88). ?DOC(" Stable report signature for M19 fixture gates.\n"). -spec report_signature(report()) -> binary(). report_signature(Report) -> Entries = gleam@list:map( erlang:element(2, Report), fun(Outcome) -> <<<<<<<<(scenario_label(erlang:element(2, Outcome)))/binary, "="/utf8>>/binary, (pass_fail_label(Outcome))/binary>>/binary, ":"/utf8>>/binary, (erlang:element(4, Outcome))/binary>> end ), join_with(<<";"/utf8>>, Entries).