-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] ->
<<<<Value@1/binary, Separator/binary>>/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).