Skip to content

Commit d5255a7

Browse files
committed
Add gn-ten distributed context proof
1 parent 6d17d5e commit d5255a7

12 files changed

Lines changed: 557 additions & 0 deletions

File tree

.blitz/test_state_v1/indexes/task_states.ndjson

Lines changed: 53 additions & 0 deletions
Large diffs are not rendered by default.

build_support/weld.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ defmodule StackLab.Build.WeldContract do
3636
"examples/replay_roundtrip",
3737
"examples/cost_roundtrip",
3838
"examples/context_abi_roundtrip",
39+
"examples/gn_ten_distributed_stack",
3940
"examples/nshkr_router_fabric_roundtrip",
4041
"examples/gepa_platform_roundtrip",
4142
"examples/trinity_platform_roundtrip",

build_support/workspace_contract.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ defmodule StackLab.Build.WorkspaceContract do
4141
"examples/replay_roundtrip",
4242
"examples/cost_roundtrip",
4343
"examples/context_abi_roundtrip",
44+
"examples/gn_ten_distributed_stack",
4445
"examples/nshkr_router_fabric_roundtrip",
4546
"examples/toy_document_review",
4647
"examples/synapse_product_acceptance",
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[
2+
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
3+
]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# StackLab Gn-Ten Distributed Stack
2+
3+
`examples/gn_ten_distributed_stack` is the local distributed proof app for the
4+
gn-ten stack.
5+
6+
Phase 8 owns the first context proof:
7+
8+
- run the fugu single-node Context ABI roundtrip as the monolith baseline;
9+
- boot a local peer-mode context topology with StackLab node lab;
10+
- verify owner facade modules register owner-defined groups on distinct peers;
11+
- scan distributed proof envelopes before accepting the receipt;
12+
- record that this is a local Erlang distribution proof, not production
13+
security or release packaging proof.
14+
15+
## Commands
16+
17+
```bash
18+
mix stack_lab.gn_ten.distributed.prove --profile context_6_node --json
19+
```
20+
21+
## QC
22+
23+
```bash
24+
mix format --check-formatted
25+
mix test
26+
```
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
defmodule Mix.Tasks.StackLab.GnTen.Distributed.Prove do
2+
@moduledoc "Runs local distributed gn-ten proof scenarios."
3+
@shortdoc "Runs local distributed gn-ten proof scenarios"
4+
5+
use Mix.Task
6+
7+
alias StackLab.Examples.GnTenDistributedStack
8+
9+
@impl true
10+
def run(args) do
11+
Mix.Task.run("app.start")
12+
13+
{opts, _argv, invalid} =
14+
OptionParser.parse(args,
15+
strict: [
16+
profile: :string,
17+
topology: :string,
18+
json: :boolean
19+
]
20+
)
21+
22+
if invalid != [], do: Mix.raise("invalid options: #{inspect(invalid)}")
23+
24+
case Keyword.get(opts, :profile, "context_6_node") do
25+
"context_6_node" ->
26+
run_context(opts)
27+
28+
other ->
29+
Mix.raise("unsupported distributed proof profile: #{other}")
30+
end
31+
end
32+
33+
defp run_context(opts) do
34+
json? = Keyword.get(opts, :json, false)
35+
36+
proof_opts =
37+
opts
38+
|> Keyword.take([:topology])
39+
|> Enum.map(fn {:topology, path} -> {:topology_path, path} end)
40+
41+
case GnTenDistributedStack.run_context_6_node(proof_opts) do
42+
{:ok, receipt} when json? ->
43+
Mix.shell().info(GnTenDistributedStack.to_json!(receipt))
44+
45+
{:ok, receipt} ->
46+
Mix.shell().info("status=#{receipt.status}")
47+
Mix.shell().info("receipt_ref=#{receipt.receipt_ref}")
48+
Mix.shell().info("topology_ref=#{receipt.topology_ref}")
49+
50+
{:error, reason} ->
51+
Mix.raise("distributed proof failed: #{inspect(reason)}")
52+
end
53+
end
54+
end
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
defmodule StackLab.Examples.GnTenDistributedStack.Receipt do
2+
@moduledoc "Distributed gn-ten proof receipt."
3+
4+
@enforce_keys [
5+
:receipt_ref,
6+
:schema_version,
7+
:status,
8+
:profile,
9+
:topology_ref,
10+
:monolith_baseline_receipt_ref,
11+
:context_packet_ref,
12+
:context_packet_hash,
13+
:authority_ref,
14+
:trace_refs,
15+
:node_lab_run,
16+
:distributed_envelope_scan,
17+
:node_placement,
18+
:does_not_prove
19+
]
20+
defstruct @enforce_keys
21+
22+
@type t :: %__MODULE__{
23+
receipt_ref: String.t(),
24+
schema_version: String.t(),
25+
status: :pass | :open_defect,
26+
profile: String.t(),
27+
topology_ref: String.t(),
28+
monolith_baseline_receipt_ref: String.t(),
29+
context_packet_ref: String.t(),
30+
context_packet_hash: String.t(),
31+
authority_ref: String.t(),
32+
trace_refs: [String.t()],
33+
node_lab_run: map(),
34+
distributed_envelope_scan: map(),
35+
node_placement: map(),
36+
does_not_prove: [String.t()]
37+
}
38+
end
39+
40+
defmodule StackLab.Examples.GnTenDistributedStack do
41+
@moduledoc """
42+
Local distributed gn-ten proof scenarios.
43+
"""
44+
45+
alias StackLab.Examples.GnTenDistributedStack.Receipt
46+
47+
@schema_version "stack_lab.gn_ten_distributed_stack.context_6_node.v1"
48+
@profile "context_6_node"
49+
@envelope_schema_version "stack_lab.distributed_envelope.v1"
50+
@context_roundtrip Module.concat([StackLab, Examples, ContextABIRoundtrip])
51+
@envelope_scanner Module.concat([StackLab, GnTenNodeLab, EnvelopeScanner])
52+
@runner Module.concat([StackLab, GnTenNodeLab, Runner])
53+
@json Module.concat([Jason])
54+
55+
@spec run_context_6_node(keyword()) :: {:ok, Receipt.t()} | {:error, term()}
56+
def run_context_6_node(opts \\ []) when is_list(opts) do
57+
topology_path = Keyword.get_lazy(opts, :topology_path, &default_context_topology_path/0)
58+
state_path = Keyword.get_lazy(opts, :state_path, &default_state_path/0)
59+
60+
with {:ok, _started} <- Application.ensure_all_started(:aitrace),
61+
{:ok, baseline} <- call(@context_roundtrip, :run, []),
62+
{:ok, node_lab_run} <-
63+
call(@runner, :up, [
64+
topology_path,
65+
[
66+
state_path: state_path,
67+
run_id: "context-6-node",
68+
keep?: false
69+
]
70+
]) do
71+
envelope_scan =
72+
call(@envelope_scanner, :scan_many, [
73+
envelopes(baseline, node_lab_run),
74+
[supported_schema_versions: [@envelope_schema_version]]
75+
])
76+
77+
{:ok,
78+
%Receipt{
79+
receipt_ref: receipt_ref(baseline),
80+
schema_version: @schema_version,
81+
status: status(baseline, node_lab_run, envelope_scan),
82+
profile: @profile,
83+
topology_ref: node_lab_run["topology_ref"],
84+
monolith_baseline_receipt_ref: baseline.receipt_ref,
85+
context_packet_ref: baseline.context_packet_ref,
86+
context_packet_hash: baseline.context_packet_hash,
87+
authority_ref: baseline.authority_ref,
88+
trace_refs: baseline.trace_refs,
89+
node_lab_run: node_lab_run,
90+
distributed_envelope_scan: envelope_scan,
91+
node_placement: node_placement(node_lab_run),
92+
does_not_prove: [
93+
"production distribution security",
94+
"release artifact boot",
95+
"live provider behavior",
96+
"fault recovery",
97+
"TRINITY or GEPA quality"
98+
]
99+
}}
100+
end
101+
end
102+
103+
@spec to_map(Receipt.t()) :: map()
104+
def to_map(%Receipt{} = receipt), do: json_safe(receipt)
105+
106+
@spec to_json!(Receipt.t()) :: String.t()
107+
def to_json!(%Receipt{} = receipt) do
108+
call(@json, :encode!, [to_map(receipt), [pretty: true]])
109+
end
110+
111+
defp envelopes(baseline, node_lab_run) do
112+
node_lab_run
113+
|> Map.fetch!("boot_receipts")
114+
|> Enum.map(fn node ->
115+
%{
116+
envelope_ref: "distributed-envelope://#{node["node_id"]}",
117+
schema_version: @envelope_schema_version,
118+
tenant_ref: "tenant://context-abi/demo",
119+
correlation_ref: "corr://context-abi/distributed/#{node["node_id"]}",
120+
idempotency_key: "idem://context-abi/distributed/#{node["node_id"]}",
121+
origin_node_ref: "node://stack_lab/controller",
122+
target_profile: node["profile"],
123+
authority_ref: baseline.authority_ref,
124+
redaction_class: "bounded_refs_only",
125+
payload_mode: "refs_only",
126+
trace_ref: List.first(baseline.trace_refs),
127+
issued_at: "2026-05-25T00:00:00Z",
128+
context_packet_ref: baseline.context_packet_ref,
129+
context_packet_hash: baseline.context_packet_hash,
130+
owner_group_membership: node["owner_group_membership"]
131+
}
132+
end)
133+
end
134+
135+
defp node_placement(node_lab_run) do
136+
boot_receipts = Map.fetch!(node_lab_run, "boot_receipts")
137+
node_names = Enum.map(boot_receipts, &Map.fetch!(&1, "node"))
138+
139+
%{
140+
controller_included?: true,
141+
domain_node_count: length(node_names),
142+
distinct_domain_nodes?: length(node_names) == length(Enum.uniq(node_names)),
143+
nodes: node_names
144+
}
145+
end
146+
147+
defp status(baseline, node_lab_run, envelope_scan) do
148+
if baseline.status == :pass and node_lab_run["status"] == "pass" and
149+
envelope_scan["status"] == "pass" do
150+
:pass
151+
else
152+
:open_defect
153+
end
154+
end
155+
156+
defp receipt_ref(baseline) do
157+
suffix =
158+
baseline.context_packet_hash
159+
|> String.replace_prefix("sha256:", "")
160+
|> String.slice(0, 16)
161+
162+
"gn-ten-distributed-context://#{suffix}"
163+
end
164+
165+
defp default_context_topology_path do
166+
Path.expand("../../../priv/topologies/context_6_node.exs", __DIR__)
167+
end
168+
169+
defp default_state_path do
170+
Path.join(System.tmp_dir!(), "stack_lab_gn_ten_distributed_context_6_node.json")
171+
end
172+
173+
defp json_safe(%_struct{} = value), do: value |> Map.from_struct() |> json_safe()
174+
175+
defp json_safe(value) when is_map(value) do
176+
Map.new(value, fn {key, nested} -> {to_string(key), json_safe(nested)} end)
177+
end
178+
179+
defp json_safe(values) when is_list(values), do: Enum.map(values, &json_safe/1)
180+
defp json_safe(value) when is_atom(value), do: Atom.to_string(value)
181+
defp json_safe(value), do: value
182+
183+
defp call(module, function, args)
184+
when is_atom(module) and is_atom(function) and is_list(args) do
185+
unless Code.ensure_loaded?(module) do
186+
raise ArgumentError, "required module is unavailable: #{inspect(module)}"
187+
end
188+
189+
apply(module, function, args)
190+
end
191+
end
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
defmodule StackLab.GnTenDistributedStack.MixProject do
2+
use Mix.Project
3+
4+
def project do
5+
[
6+
app: :stack_lab_gn_ten_distributed_stack,
7+
version: "0.1.0",
8+
elixir: "~> 1.19",
9+
start_permanent: Mix.env() == :prod,
10+
deps: deps(),
11+
aliases: aliases(),
12+
docs: [main: "readme", extras: ["README.md"]],
13+
dialyzer: [plt_add_deps: :apps_direct],
14+
name: "StackLab Gn-Ten Distributed Stack",
15+
description: "Local distributed gn-ten StackLab proof app"
16+
]
17+
end
18+
19+
def application do
20+
[extra_applications: [:crypto, :logger]]
21+
end
22+
23+
def cli do
24+
[preferred_envs: [ci: :test]]
25+
end
26+
27+
defp deps do
28+
[
29+
{:stack_lab_gn_ten_node_lab, path: "../../support/gn_ten_node_lab", runtime: false},
30+
{:stack_lab_context_abi_roundtrip, path: "../context_abi_roundtrip", runtime: false},
31+
{:app_kit_mezzanine_bridge,
32+
path: "../../../app_kit/bridges/mezzanine_bridge", runtime: false},
33+
{:mezzanine_execution_engine,
34+
path: "../../../mezzanine/core/execution_engine", runtime: false},
35+
{:citadel_context_authority_contract,
36+
path: "../../../citadel/core/context_authority_contract", runtime: false},
37+
{:outer_brain_context_abi,
38+
path: "../../../outer_brain/core/context_abi", override: true, runtime: false},
39+
{:execution_plane,
40+
path: "../../../execution_plane/core/execution_plane", override: true, runtime: false},
41+
{:aitrace, path: "../../../AITrace", override: true, runtime: false},
42+
{:jason, "~> 1.4", runtime: false},
43+
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
44+
{:dialyxir, "~> 1.4", only: :dev, runtime: false},
45+
{:ex_doc, "~> 0.40.1", only: [:dev, :test], runtime: false}
46+
]
47+
end
48+
49+
defp aliases do
50+
[
51+
ci: [
52+
"deps.get",
53+
"format --check-formatted",
54+
"compile --warnings-as-errors",
55+
"test",
56+
"credo --strict",
57+
"docs"
58+
]
59+
]
60+
end
61+
end

0 commit comments

Comments
 (0)