diff --git a/apps/guildhall_ops_db/lib/guildhall/ops_db/guild.ex b/apps/guildhall_ops_db/lib/guildhall/ops_db/guild.ex
new file mode 100644
index 0000000..509c6ea
--- /dev/null
+++ b/apps/guildhall_ops_db/lib/guildhall/ops_db/guild.ex
@@ -0,0 +1,57 @@
+defmodule Guildhall.OpsDb.Guild do
+ use Ecto.Schema
+ import Ecto.Changeset
+
+ @type t :: %__MODULE__{}
+
+ @min_guild_id 0x010
+ @max_guild_id 0x3FF
+
+ schema "guilds" do
+ field :guild_id, :integer
+ field :name, :string
+ field :slug, :string
+ field :guild_type, :string
+ field :description, :string
+ field :trust_domain, :string
+ field :contact_did, :string
+ field :registrant_did, :string
+ field :registration_ceremony_id, :string
+ field :status, :string, default: "pending_approval"
+ field :enrollment_accord_ref, :string
+ field :metadata, :map, default: %{}
+
+ has_many :guild_schematics, Guildhall.OpsDb.GuildSchematic
+ has_many :guild_memberships, Guildhall.OpsDb.GuildMembership
+
+ timestamps(type: :utc_datetime_usec)
+ end
+
+ def changeset(guild, attrs) do
+ guild
+ |> cast(attrs, [
+ :guild_id,
+ :name,
+ :slug,
+ :guild_type,
+ :description,
+ :trust_domain,
+ :contact_did,
+ :registrant_did,
+ :registration_ceremony_id,
+ :status,
+ :enrollment_accord_ref,
+ :metadata
+ ])
+ |> validate_required([:guild_id, :name, :slug, :guild_type, :contact_did, :registrant_did])
+ |> validate_inclusion(:guild_type, ~w(msp isv nsp))
+ |> validate_inclusion(:status, ~w(pending_approval approved denied active suspended))
+ |> validate_number(:guild_id,
+ greater_than_or_equal_to: @min_guild_id,
+ less_than_or_equal_to: @max_guild_id
+ )
+ |> unique_constraint(:guild_id)
+ |> unique_constraint(:slug)
+ |> unique_constraint(:name)
+ end
+end
diff --git a/apps/guildhall_ops_db/lib/guildhall/ops_db/guild_membership.ex b/apps/guildhall_ops_db/lib/guildhall/ops_db/guild_membership.ex
new file mode 100644
index 0000000..e1c8365
--- /dev/null
+++ b/apps/guildhall_ops_db/lib/guildhall/ops_db/guild_membership.ex
@@ -0,0 +1,45 @@
+defmodule Guildhall.OpsDb.GuildMembership do
+ use Ecto.Schema
+ import Ecto.Changeset
+
+ @type t :: %__MODULE__{}
+
+ schema "guild_memberships" do
+ belongs_to :guild, Guildhall.OpsDb.Guild
+
+ field :user_did, :string
+ field :user_email, :string
+ field :display_name, :string
+ field :keycloak_sub, :string
+ field :role, :string, default: "apprentice"
+ field :status, :string, default: "pending"
+ field :membership_ceremony_id, :string
+ field :approved_by_did, :string
+ field :approved_at, :utc_datetime_usec
+ field :metadata, :map, default: %{}
+
+ timestamps(type: :utc_datetime_usec)
+ end
+
+ def changeset(membership, attrs) do
+ membership
+ |> cast(attrs, [
+ :guild_id,
+ :user_did,
+ :user_email,
+ :display_name,
+ :keycloak_sub,
+ :role,
+ :status,
+ :membership_ceremony_id,
+ :approved_by_did,
+ :approved_at,
+ :metadata
+ ])
+ |> validate_required([:guild_id, :user_did, :user_email, :keycloak_sub])
+ |> validate_inclusion(:role, ~w(apprentice journeyman master))
+ |> validate_inclusion(:status, ~w(pending approved denied active suspended removed))
+ |> foreign_key_constraint(:guild_id)
+ |> unique_constraint([:guild_id, :user_did])
+ end
+end
diff --git a/apps/guildhall_ops_db/lib/guildhall/ops_db/guild_memberships.ex b/apps/guildhall_ops_db/lib/guildhall/ops_db/guild_memberships.ex
new file mode 100644
index 0000000..c7bd4c8
--- /dev/null
+++ b/apps/guildhall_ops_db/lib/guildhall/ops_db/guild_memberships.ex
@@ -0,0 +1,58 @@
+defmodule Guildhall.OpsDb.GuildMemberships do
+ import Ecto.Query, only: [from: 2]
+ alias Guildhall.OpsDb.{Repo, GuildMembership}
+
+ def list_memberships(guild_id) do
+ Repo.all(from(m in GuildMembership, where: m.guild_id == ^guild_id, order_by: [asc: m.inserted_at]))
+ end
+
+ def get_membership!(id), do: Repo.get!(GuildMembership, id)
+
+ def active_members(guild_id) do
+ Repo.all(from(m in GuildMembership, where: m.guild_id == ^guild_id and m.status == "active"))
+ end
+
+ def masters_for_guild(guild_id) do
+ Repo.all(
+ from(m in GuildMembership,
+ where: m.guild_id == ^guild_id and m.role == "master" and m.status == "active"
+ )
+ )
+ end
+
+ def find_membership(guild_id, user_did) do
+ Repo.one(from(m in GuildMembership, where: m.guild_id == ^guild_id and m.user_did == ^user_did))
+ end
+
+ def request_membership(attrs) do
+ %GuildMembership{}
+ |> GuildMembership.changeset(attrs)
+ |> Repo.insert()
+ end
+
+ def approve_membership(%GuildMembership{} = membership, approver_did) do
+ membership
+ |> GuildMembership.changeset(%{
+ status: "active",
+ approved_by_did: approver_did,
+ approved_at: DateTime.utc_now()
+ })
+ |> Repo.update()
+ end
+
+ def deny_membership(%GuildMembership{} = membership, approver_did) do
+ membership
+ |> GuildMembership.changeset(%{
+ status: "denied",
+ approved_by_did: approver_did,
+ approved_at: DateTime.utc_now()
+ })
+ |> Repo.update()
+ end
+
+ def update_role(%GuildMembership{} = membership, new_role) do
+ membership
+ |> GuildMembership.changeset(%{role: new_role})
+ |> Repo.update()
+ end
+end
diff --git a/apps/guildhall_ops_db/lib/guildhall/ops_db/guild_schematic.ex b/apps/guildhall_ops_db/lib/guildhall/ops_db/guild_schematic.ex
new file mode 100644
index 0000000..3063412
--- /dev/null
+++ b/apps/guildhall_ops_db/lib/guildhall/ops_db/guild_schematic.ex
@@ -0,0 +1,42 @@
+defmodule Guildhall.OpsDb.GuildSchematic do
+ use Ecto.Schema
+ import Ecto.Changeset
+
+ @type t :: %__MODULE__{}
+
+ schema "guild_schematics" do
+ belongs_to :guild, Guildhall.OpsDb.Guild
+
+ field :template_name, :string
+ field :schematic_name, :string
+ field :schematic_version, :string
+ field :tree_hash, :string
+ field :binding_id, :string
+ field :realization_id, :string
+ field :status, :string, default: "pending"
+ field :customization_params, :map, default: %{}
+ field :realization_snapshot, :map, default: %{}
+
+ timestamps(type: :utc_datetime_usec)
+ end
+
+ def changeset(schematic, attrs) do
+ schematic
+ |> cast(attrs, [
+ :guild_id,
+ :template_name,
+ :schematic_name,
+ :schematic_version,
+ :tree_hash,
+ :binding_id,
+ :realization_id,
+ :status,
+ :customization_params,
+ :realization_snapshot
+ ])
+ |> validate_required([:guild_id, :template_name, :schematic_name, :schematic_version])
+ |> validate_inclusion(:status, ~w(pending forked binding_created realizing realized partially_realized failed))
+ |> foreign_key_constraint(:guild_id)
+ |> unique_constraint([:schematic_name, :schematic_version])
+ end
+end
diff --git a/apps/guildhall_ops_db/lib/guildhall/ops_db/guild_schematics.ex b/apps/guildhall_ops_db/lib/guildhall/ops_db/guild_schematics.ex
new file mode 100644
index 0000000..cb69147
--- /dev/null
+++ b/apps/guildhall_ops_db/lib/guildhall/ops_db/guild_schematics.ex
@@ -0,0 +1,30 @@
+defmodule Guildhall.OpsDb.GuildSchematics do
+ import Ecto.Query, only: [from: 2]
+ alias Guildhall.OpsDb.{Repo, GuildSchematic}
+
+ def get_for_guild(guild_id) do
+ Repo.one(
+ from(gs in GuildSchematic,
+ where: gs.guild_id == ^guild_id,
+ order_by: [desc: gs.inserted_at],
+ limit: 1
+ )
+ )
+ end
+
+ def create(attrs) do
+ %GuildSchematic{}
+ |> GuildSchematic.changeset(attrs)
+ |> Repo.insert()
+ end
+
+ def update_schematic(%GuildSchematic{} = gs, attrs) do
+ gs
+ |> GuildSchematic.changeset(attrs)
+ |> Repo.update()
+ end
+
+ def update_realization_snapshot(%GuildSchematic{} = gs, snapshot) do
+ update_schematic(gs, %{realization_snapshot: snapshot})
+ end
+end
diff --git a/apps/guildhall_ops_db/lib/guildhall/ops_db/guilds.ex b/apps/guildhall_ops_db/lib/guildhall/ops_db/guilds.ex
new file mode 100644
index 0000000..9211c3d
--- /dev/null
+++ b/apps/guildhall_ops_db/lib/guildhall/ops_db/guilds.ex
@@ -0,0 +1,63 @@
+defmodule Guildhall.OpsDb.Guilds do
+ import Ecto.Query
+ alias Guildhall.OpsDb.{Repo, Guild, GuildMembership}
+ alias Ecto.Multi
+
+ def list_guilds(filters \\ []) do
+ Guild
+ |> maybe_filter_status(filters[:status])
+ |> order_by([g], desc: g.inserted_at)
+ |> Repo.all()
+ end
+
+ def get_guild!(id), do: Repo.get!(Guild, id)
+
+ def get_guild_by_slug(slug), do: Repo.get_by(Guild, slug: slug)
+
+ def create_guild(attrs) do
+ %Guild{}
+ |> Guild.changeset(attrs)
+ |> Repo.insert()
+ end
+
+ def update_guild(%Guild{} = guild, attrs) do
+ guild
+ |> Guild.changeset(attrs)
+ |> Repo.update()
+ end
+
+ def next_guild_id do
+ case Repo.one(from(g in Guild, select: max(g.guild_id))) do
+ nil -> 0x010
+ max_id -> max_id + 1
+ end
+ end
+
+ def approve_guild(%Guild{} = guild) do
+ now = DateTime.utc_now()
+
+ Multi.new()
+ |> Multi.update(:guild, Guild.changeset(guild, %{status: "approved"}))
+ |> Multi.insert(:master_membership, GuildMembership.changeset(%GuildMembership{}, %{
+ guild_id: guild.id,
+ user_did: guild.registrant_did,
+ user_email: guild.contact_did,
+ display_name: "Guild Registrant",
+ keycloak_sub: "",
+ role: "master",
+ status: "active",
+ approved_by_did: "system",
+ approved_at: now
+ }), on_conflict: :nothing, conflict_target: [:guild_id, :user_did])
+ |> Repo.transaction()
+ end
+
+ def guild_count(status \\ nil) do
+ Guild
+ |> maybe_filter_status(status)
+ |> Repo.aggregate(:count)
+ end
+
+ defp maybe_filter_status(query, nil), do: query
+ defp maybe_filter_status(query, status), do: where(query, [g], g.status == ^status)
+end
diff --git a/apps/guildhall_ops_db/priv/repo/migrations/20260515184247_create_guilds.exs b/apps/guildhall_ops_db/priv/repo/migrations/20260515184247_create_guilds.exs
new file mode 100644
index 0000000..21f709e
--- /dev/null
+++ b/apps/guildhall_ops_db/priv/repo/migrations/20260515184247_create_guilds.exs
@@ -0,0 +1,34 @@
+defmodule Guildhall.OpsDb.Repo.Migrations.CreateGuilds do
+ use Ecto.Migration
+
+ def change do
+ create table(:guilds) do
+ add :guild_id, :integer, null: false
+ add :name, :string, null: false
+ add :slug, :string, null: false
+ add :guild_type, :string, null: false
+ add :description, :text
+ add :trust_domain, :string
+ add :contact_did, :string, null: false
+ add :registrant_did, :string, null: false
+ add :registration_ceremony_id, :string
+ add :status, :string, null: false, default: "pending_approval"
+ add :enrollment_accord_ref, :string
+ add :metadata, :map, default: %{}
+
+ timestamps(type: :utc_datetime_usec)
+ end
+
+ create unique_index(:guilds, [:guild_id])
+ create unique_index(:guilds, [:slug])
+ create unique_index(:guilds, [:name])
+ create index(:guilds, [:status])
+ create index(:guilds, [:guild_type])
+ create index(:guilds, [:registrant_did])
+
+ execute(
+ "ALTER TABLE guilds ADD CONSTRAINT guilds_guild_id_range CHECK (guild_id >= 16 AND guild_id <= 1023)",
+ "ALTER TABLE guilds DROP CONSTRAINT guilds_guild_id_range"
+ )
+ end
+end
diff --git a/apps/guildhall_ops_db/priv/repo/migrations/20260515185035_create_guild_schematics.exs b/apps/guildhall_ops_db/priv/repo/migrations/20260515185035_create_guild_schematics.exs
new file mode 100644
index 0000000..2f0ab3c
--- /dev/null
+++ b/apps/guildhall_ops_db/priv/repo/migrations/20260515185035_create_guild_schematics.exs
@@ -0,0 +1,24 @@
+defmodule Guildhall.OpsDb.Repo.Migrations.CreateGuildSchematics do
+ use Ecto.Migration
+
+ def change do
+ create table(:guild_schematics) do
+ add :guild_id, references(:guilds, on_delete: :restrict), null: false
+ add :template_name, :string, null: false
+ add :schematic_name, :string, null: false
+ add :schematic_version, :string, null: false
+ add :tree_hash, :string
+ add :binding_id, :string
+ add :realization_id, :string
+ add :status, :string, null: false, default: "pending"
+ add :customization_params, :map, default: %{}
+ add :realization_snapshot, :map, default: %{}
+
+ timestamps(type: :utc_datetime_usec)
+ end
+
+ create index(:guild_schematics, [:guild_id])
+ create index(:guild_schematics, [:status])
+ create unique_index(:guild_schematics, [:schematic_name, :schematic_version])
+ end
+end
diff --git a/apps/guildhall_ops_db/priv/repo/migrations/20260515185309_create_guild_memberships.exs b/apps/guildhall_ops_db/priv/repo/migrations/20260515185309_create_guild_memberships.exs
new file mode 100644
index 0000000..7fa65e7
--- /dev/null
+++ b/apps/guildhall_ops_db/priv/repo/migrations/20260515185309_create_guild_memberships.exs
@@ -0,0 +1,27 @@
+defmodule Guildhall.OpsDb.Repo.Migrations.CreateGuildMemberships do
+ use Ecto.Migration
+
+ def change do
+ create table(:guild_memberships) do
+ add :guild_id, references(:guilds, on_delete: :restrict), null: false
+ add :user_did, :string, null: false
+ add :user_email, :string, null: false
+ add :display_name, :string
+ add :keycloak_sub, :string, null: false
+ add :role, :string, null: false, default: "apprentice"
+ add :status, :string, null: false, default: "pending"
+ add :membership_ceremony_id, :string
+ add :approved_by_did, :string
+ add :approved_at, :utc_datetime_usec
+ add :metadata, :map, default: %{}
+
+ timestamps(type: :utc_datetime_usec)
+ end
+
+ create unique_index(:guild_memberships, [:guild_id, :user_did])
+ create index(:guild_memberships, [:guild_id])
+ create index(:guild_memberships, [:user_did])
+ create index(:guild_memberships, [:status])
+ create index(:guild_memberships, [:keycloak_sub])
+ end
+end
diff --git a/apps/guildhall_orchestrator/lib/guildhall/orchestrator/application.ex b/apps/guildhall_orchestrator/lib/guildhall/orchestrator/application.ex
index 49946ba..85639aa 100644
--- a/apps/guildhall_orchestrator/lib/guildhall/orchestrator/application.ex
+++ b/apps/guildhall_orchestrator/lib/guildhall/orchestrator/application.ex
@@ -5,7 +5,8 @@ defmodule Guildhall.Orchestrator.Application do
@impl true
def start(_type, _args) do
children = [
- Guildhall.Orchestrator.CeremonyOrchestrator
+ Guildhall.Orchestrator.CeremonyOrchestrator,
+ Guildhall.Orchestrator.RealizationPoller
]
opts = [strategy: :one_for_one, name: Guildhall.Orchestrator.Supervisor]
diff --git a/apps/guildhall_orchestrator/lib/guildhall/orchestrator/ceremony_client.ex b/apps/guildhall_orchestrator/lib/guildhall/orchestrator/ceremony_client.ex
new file mode 100644
index 0000000..5316fd0
--- /dev/null
+++ b/apps/guildhall_orchestrator/lib/guildhall/orchestrator/ceremony_client.ex
@@ -0,0 +1,109 @@
+defmodule Guildhall.Orchestrator.CeremonyClient do
+ @moduledoc false
+
+ alias Ceremony.V1.{
+ CeremonyService.Stub,
+ CreateCeremonyRequest,
+ ApproveCeremonyRequest,
+ DenyCeremonyRequest,
+ GetCeremonyRequest,
+ ListPendingCeremoniesRequest,
+ CeremonySubjectMsg
+ }
+
+ def create_guild_registration_ceremony(guild_name, registrant_did, approver_did) do
+ request = %CreateCeremonyRequest{
+ ceremony_type: "single_approval",
+ subject: %CeremonySubjectMsg{
+ subject_type: "custom",
+ reference_id: guild_name,
+ description: "Guild registration: #{guild_name}",
+ metadata: %{"registrant_did" => registrant_did, "approver_did" => approver_did}
+ },
+ required_approvals: 1,
+ approver_roles: ["hub_operator"],
+ ttl_hours: 168
+ }
+
+ with {:ok, channel} <- connect(),
+ {:ok, response} <- Stub.create_ceremony(channel, request) do
+ GRPC.Stub.disconnect(channel)
+
+ if response.error != "" do
+ {:error, response.error}
+ else
+ {:ok, response}
+ end
+ end
+ end
+
+ def approve_ceremony(ceremony_id, approver_did, role \\ "hub_operator") do
+ request = %ApproveCeremonyRequest{
+ ceremony_id: ceremony_id,
+ approver_identity: approver_did,
+ approver_role: role,
+ comment: ""
+ }
+
+ with {:ok, channel} <- connect(),
+ {:ok, response} <- Stub.approve_ceremony(channel, request) do
+ GRPC.Stub.disconnect(channel)
+
+ if response.error != "" do
+ {:error, response.error}
+ else
+ {:ok, response}
+ end
+ end
+ end
+
+ def deny_ceremony(ceremony_id, approver_did, role, comment) do
+ request = %DenyCeremonyRequest{
+ ceremony_id: ceremony_id,
+ approver_identity: approver_did,
+ approver_role: role,
+ comment: comment
+ }
+
+ with {:ok, channel} <- connect(),
+ {:ok, response} <- Stub.deny_ceremony(channel, request) do
+ GRPC.Stub.disconnect(channel)
+
+ if response.error != "" do
+ {:error, response.error}
+ else
+ {:ok, response}
+ end
+ end
+ end
+
+ def get_ceremony(ceremony_id) do
+ request = %GetCeremonyRequest{ceremony_id: ceremony_id}
+
+ with {:ok, channel} <- connect(),
+ {:ok, response} <- Stub.get_ceremony(channel, request) do
+ GRPC.Stub.disconnect(channel)
+
+ if response.error != "" do
+ {:error, response.error}
+ else
+ {:ok, response}
+ end
+ end
+ end
+
+ def list_pending_ceremonies(intent_id \\ "") do
+ request = %ListPendingCeremoniesRequest{intent_id: intent_id}
+
+ with {:ok, channel} <- connect(),
+ {:ok, response} <- Stub.list_pending_ceremonies(channel, request) do
+ GRPC.Stub.disconnect(channel)
+ {:ok, response.ceremonies}
+ end
+ end
+
+ defp connect do
+ url = Application.get_env(:guildhall_orchestrator, :ceremony_service_url, "localhost:50053")
+ GRPC.Stub.connect(url)
+ end
+end
diff --git a/apps/guildhall_orchestrator/lib/guildhall/orchestrator/proto/ceremony/v1/ceremony.pb.ex b/apps/guildhall_orchestrator/lib/guildhall/orchestrator/proto/ceremony/v1/ceremony.pb.ex
new file mode 100644
index 0000000..8917b52
--- /dev/null
+++ b/apps/guildhall_orchestrator/lib/guildhall/orchestrator/proto/ceremony/v1/ceremony.pb.ex
@@ -0,0 +1,296 @@
+defmodule Ceremony.V1.CreateCeremonyRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.CreateCeremonyRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :ceremony_type, 1, type: :string, json_name: "ceremonyType"
+ field :subject, 2, type: Ceremony.V1.CeremonySubjectMsg
+ field :required_approvals, 3, type: :uint32, json_name: "requiredApprovals"
+ field :approver_roles, 4, repeated: true, type: :string, json_name: "approverRoles"
+ field :ttl_hours, 5, type: :uint32, json_name: "ttlHours"
+ field :intent_id, 6, type: :string, json_name: "intentId"
+ field :run_id, 7, type: :string, json_name: "runId"
+ field :pr_number, 8, type: :uint64, json_name: "prNumber"
+ field :remote_name, 9, type: :string, json_name: "remoteName"
+end
+
+defmodule Ceremony.V1.CreateCeremonyResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.CreateCeremonyResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :ceremony_id, 1, type: :string, json_name: "ceremonyId"
+ field :status, 2, type: :string
+ field :expires_at, 3, type: Google.Protobuf.Timestamp, json_name: "expiresAt"
+ field :error, 4, type: :string
+end
+
+defmodule Ceremony.V1.ApproveCeremonyRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.ApproveCeremonyRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :ceremony_id, 1, type: :string, json_name: "ceremonyId"
+ field :approver_identity, 2, type: :string, json_name: "approverIdentity"
+ field :approver_role, 3, type: :string, json_name: "approverRole"
+ field :comment, 4, type: :string
+end
+
+defmodule Ceremony.V1.ApproveCeremonyResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.ApproveCeremonyResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :success, 1, type: :bool
+ field :status, 2, type: :string
+ field :error, 3, type: :string
+end
+
+defmodule Ceremony.V1.DenyCeremonyRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.DenyCeremonyRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :ceremony_id, 1, type: :string, json_name: "ceremonyId"
+ field :approver_identity, 2, type: :string, json_name: "approverIdentity"
+ field :approver_role, 3, type: :string, json_name: "approverRole"
+ field :comment, 4, type: :string
+end
+
+defmodule Ceremony.V1.DenyCeremonyResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.DenyCeremonyResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :success, 1, type: :bool
+ field :status, 2, type: :string
+ field :error, 3, type: :string
+end
+
+defmodule Ceremony.V1.CancelCeremonyRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.CancelCeremonyRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :ceremony_id, 1, type: :string, json_name: "ceremonyId"
+end
+
+defmodule Ceremony.V1.CancelCeremonyResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.CancelCeremonyResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :success, 1, type: :bool
+ field :error, 2, type: :string
+end
+
+defmodule Ceremony.V1.GetCeremonyRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.GetCeremonyRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :ceremony_id, 1, type: :string, json_name: "ceremonyId"
+end
+
+defmodule Ceremony.V1.GetCeremonyResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.GetCeremonyResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :ceremony_id, 1, type: :string, json_name: "ceremonyId"
+ field :ceremony_type, 2, type: :string, json_name: "ceremonyType"
+ field :subject, 3, type: Ceremony.V1.CeremonySubjectMsg
+ field :status, 4, type: :string
+ field :required_approvals, 5, type: :uint32, json_name: "requiredApprovals"
+ field :current_approvals, 6, type: :uint32, json_name: "currentApprovals"
+ field :approvals, 7, repeated: true, type: Ceremony.V1.CeremonyApprovalMsg
+ field :created_at, 8, type: Google.Protobuf.Timestamp, json_name: "createdAt"
+ field :expires_at, 9, type: Google.Protobuf.Timestamp, json_name: "expiresAt"
+ field :intent_id, 10, type: :string, json_name: "intentId"
+ field :run_id, 11, type: :string, json_name: "runId"
+ field :pr_number, 12, type: :uint64, json_name: "prNumber"
+ field :remote_name, 13, type: :string, json_name: "remoteName"
+ field :error, 14, type: :string
+end
+
+defmodule Ceremony.V1.ListPendingCeremoniesRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.ListPendingCeremoniesRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :intent_id, 1, type: :string, json_name: "intentId"
+end
+
+defmodule Ceremony.V1.ListPendingCeremoniesResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.ListPendingCeremoniesResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :ceremonies, 1, repeated: true, type: Ceremony.V1.GetCeremonyResponse
+end
+
+defmodule Ceremony.V1.GetCeremonyProofRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.GetCeremonyProofRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :ceremony_id, 1, type: :string, json_name: "ceremonyId"
+end
+
+defmodule Ceremony.V1.GetCeremonyProofResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.GetCeremonyProofResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :ceremony_id, 1, type: :string, json_name: "ceremonyId"
+ field :status, 2, type: :string
+ field :proof_hash, 3, type: :string, json_name: "proofHash"
+ field :approvals, 4, repeated: true, type: Ceremony.V1.CeremonyApprovalMsg
+ field :resolved_at, 5, type: Google.Protobuf.Timestamp, json_name: "resolvedAt"
+ field :error, 6, type: :string
+end
+
+defmodule Ceremony.V1.CheckCeremonyRequirementRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.CheckCeremonyRequirementRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :classification, 1, type: :string
+end
+
+defmodule Ceremony.V1.CheckCeremonyRequirementResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.CheckCeremonyRequirementResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :outcome, 1, type: :string
+ field :ceremony_type, 2, type: :string, json_name: "ceremonyType"
+ field :approver_roles, 3, repeated: true, type: :string, json_name: "approverRoles"
+ field :required_approvals, 4, type: :uint32, json_name: "requiredApprovals"
+end
+
+defmodule Ceremony.V1.CeremonySubjectMsg.MetadataEntry do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.CeremonySubjectMsg.MetadataEntry",
+ map: true,
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :key, 1, type: :string
+ field :value, 2, type: :string
+end
+
+defmodule Ceremony.V1.CeremonySubjectMsg do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.CeremonySubjectMsg",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :subject_type, 1, type: :string, json_name: "subjectType"
+ field :reference_id, 2, type: :string, json_name: "referenceId"
+ field :description, 3, type: :string
+
+ field :metadata, 4,
+ repeated: true,
+ type: Ceremony.V1.CeremonySubjectMsg.MetadataEntry,
+ map: true
+end
+
+defmodule Ceremony.V1.CeremonyApprovalMsg do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "ceremony.v1.CeremonyApprovalMsg",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :approver_identity, 1, type: :string, json_name: "approverIdentity"
+ field :approver_role, 2, type: :string, json_name: "approverRole"
+ field :decision, 3, type: :string
+ field :comment, 4, type: :string
+ field :decided_at, 5, type: Google.Protobuf.Timestamp, json_name: "decidedAt"
+end
+
+defmodule Ceremony.V1.CeremonyService.Service do
+ @moduledoc false
+
+ use GRPC.Service, name: "ceremony.v1.CeremonyService", protoc_gen_elixir_version: "0.16.0"
+
+ rpc :CreateCeremony, Ceremony.V1.CreateCeremonyRequest, Ceremony.V1.CreateCeremonyResponse
+
+ rpc :ApproveCeremony, Ceremony.V1.ApproveCeremonyRequest, Ceremony.V1.ApproveCeremonyResponse
+
+ rpc :DenyCeremony, Ceremony.V1.DenyCeremonyRequest, Ceremony.V1.DenyCeremonyResponse
+
+ rpc :CancelCeremony, Ceremony.V1.CancelCeremonyRequest, Ceremony.V1.CancelCeremonyResponse
+
+ rpc :GetCeremony, Ceremony.V1.GetCeremonyRequest, Ceremony.V1.GetCeremonyResponse
+
+ rpc :ListPendingCeremonies,
+ Ceremony.V1.ListPendingCeremoniesRequest,
+ Ceremony.V1.ListPendingCeremoniesResponse
+
+ rpc :GetCeremonyProof, Ceremony.V1.GetCeremonyProofRequest, Ceremony.V1.GetCeremonyProofResponse
+
+ rpc :CheckCeremonyRequirement,
+ Ceremony.V1.CheckCeremonyRequirementRequest,
+ Ceremony.V1.CheckCeremonyRequirementResponse
+end
+
+defmodule Ceremony.V1.CeremonyService.Stub do
+ @moduledoc false
+
+ use GRPC.Stub, service: Ceremony.V1.CeremonyService.Service
+end
diff --git a/apps/guildhall_orchestrator/lib/guildhall/orchestrator/proto/schematic/v1/ffc_schematic.pb.ex b/apps/guildhall_orchestrator/lib/guildhall/orchestrator/proto/schematic/v1/ffc_schematic.pb.ex
new file mode 100644
index 0000000..1f18f2b
--- /dev/null
+++ b/apps/guildhall_orchestrator/lib/guildhall/orchestrator/proto/schematic/v1/ffc_schematic.pb.ex
@@ -0,0 +1,1130 @@
+defmodule Schematic.V1.CeremonyType do
+ @moduledoc false
+
+ use Protobuf,
+ enum: true,
+ full_name: "schematic.v1.CeremonyType",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :CEREMONY_TYPE_UNSPECIFIED, 0
+ field :CEREMONY_TYPE_AUTONOMOUS, 1
+ field :CEREMONY_TYPE_SELF_GRANT, 2
+ field :CEREMONY_TYPE_SINGLE_APPROVAL, 3
+ field :CEREMONY_TYPE_MULTI_PARTY, 4
+ field :CEREMONY_TYPE_REFERENDUM, 5
+ field :CEREMONY_TYPE_BREAK_GLASS, 6
+ field :CEREMONY_TYPE_INHERIT, 7
+end
+
+defmodule Schematic.V1.AttestationLevel do
+ @moduledoc false
+
+ use Protobuf,
+ enum: true,
+ full_name: "schematic.v1.AttestationLevel",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :ATTESTATION_LEVEL_UNSPECIFIED, 0
+ field :ATTESTATION_LEVEL_CRYPTOGRAPHIC, 1
+ field :ATTESTATION_LEVEL_AUDITABLE, 2
+ field :ATTESTATION_LEVEL_INFORMAL, 3
+end
+
+defmodule Schematic.V1.Channel do
+ @moduledoc false
+
+ use Protobuf,
+ enum: true,
+ full_name: "schematic.v1.Channel",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :CHANNEL_UNSPECIFIED, 0
+ field :CHANNEL_SHELLSTREAM, 1
+ field :CHANNEL_GIT_PR_REVIEW, 2
+ field :CHANNEL_JIRA, 3
+ field :CHANNEL_SLACK, 4
+ field :CHANNEL_EMAIL, 5
+ field :CHANNEL_CUSTOM, 6
+end
+
+defmodule Schematic.V1.ProviderKind do
+ @moduledoc false
+
+ use Protobuf,
+ enum: true,
+ full_name: "schematic.v1.ProviderKind",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :PROVIDER_KIND_UNSPECIFIED, 0
+ field :PROVIDER_KIND_KEYCLOAK, 1
+ field :PROVIDER_KIND_ENTRA, 2
+ field :PROVIDER_KIND_OKTA, 3
+ field :PROVIDER_KIND_GOOGLE, 4
+ field :PROVIDER_KIND_GENERIC_OIDC, 5
+end
+
+defmodule Schematic.V1.TrustLevel do
+ @moduledoc false
+
+ use Protobuf,
+ enum: true,
+ full_name: "schematic.v1.TrustLevel",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :TRUST_LEVEL_UNSPECIFIED, 0
+ field :TRUST_LEVEL_FEDERATED, 1
+ field :TRUST_LEVEL_HYBRID, 2
+ field :TRUST_LEVEL_SOVEREIGN, 3
+end
+
+defmodule Schematic.V1.ConsortiumRole do
+ @moduledoc false
+
+ use Protobuf,
+ enum: true,
+ full_name: "schematic.v1.ConsortiumRole",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :CONSORTIUM_ROLE_UNSPECIFIED, 0
+ field :CONSORTIUM_ROLE_FOUNDING_STAKEHOLDER, 1
+ field :CONSORTIUM_ROLE_STAKEHOLDER, 2
+ field :CONSORTIUM_ROLE_OBSERVER, 3
+end
+
+defmodule Schematic.V1.TrustTier do
+ @moduledoc false
+
+ use Protobuf,
+ enum: true,
+ full_name: "schematic.v1.TrustTier",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :TRUST_TIER_UNSPECIFIED, 0
+ field :TRUST_TIER_APPRENTICE, 1
+ field :TRUST_TIER_JOURNEYMAN, 2
+ field :TRUST_TIER_MASTER, 3
+ field :TRUST_TIER_SITE_OWNER, 4
+end
+
+defmodule Schematic.V1.PeerRelationship do
+ @moduledoc false
+
+ use Protobuf,
+ enum: true,
+ full_name: "schematic.v1.PeerRelationship",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :PEER_RELATIONSHIP_UNSPECIFIED, 0
+ field :PEER_RELATIONSHIP_TENANT, 1
+ field :PEER_RELATIONSHIP_PEER, 2
+ field :PEER_RELATIONSHIP_UPSTREAM, 3
+end
+
+defmodule Schematic.V1.CapabilityCeiling do
+ @moduledoc false
+
+ use Protobuf,
+ enum: true,
+ full_name: "schematic.v1.CapabilityCeiling",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :CAPABILITY_CEILING_UNSPECIFIED, 0
+ field :CAPABILITY_CEILING_READ, 1
+ field :CAPABILITY_CEILING_PROPOSE, 2
+ field :CAPABILITY_CEILING_MUTATE, 3
+ field :CAPABILITY_CEILING_ADMIN, 4
+end
+
+defmodule Schematic.V1.FfcRole do
+ @moduledoc false
+
+ use Protobuf,
+ enum: true,
+ full_name: "schematic.v1.FfcRole",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :FFC_ROLE_UNSPECIFIED, 0
+ field :FFC_ROLE_FOUNDING_STAKEHOLDER, 1
+ field :FFC_ROLE_STAKEHOLDER, 2
+ field :FFC_ROLE_OBSERVER, 3
+end
+
+defmodule Schematic.V1.FfcSchematicStatus do
+ @moduledoc false
+
+ use Protobuf,
+ enum: true,
+ full_name: "schematic.v1.FfcSchematicStatus",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :FFC_SCHEMATIC_STATUS_UNSPECIFIED, 0
+ field :FFC_SCHEMATIC_STATUS_DRAFT, 1
+ field :FFC_SCHEMATIC_STATUS_APPROVED, 2
+ field :FFC_SCHEMATIC_STATUS_PUBLISHED, 3
+ field :FFC_SCHEMATIC_STATUS_REALIZED, 4
+ field :FFC_SCHEMATIC_STATUS_ARCHIVED, 5
+ field :FFC_SCHEMATIC_STATUS_WITHDRAWN, 6
+end
+
+defmodule Schematic.V1.ConsortiumIdentity do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ConsortiumIdentity",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :did, 1, type: :string
+ field :realm, 2, type: :string
+ field :description, 3, type: :string
+end
+
+defmodule Schematic.V1.RotationPolicy do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.RotationPolicy",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :svid_ttl, 1, type: :string, json_name: "svidTtl"
+ field :ca_ttl, 2, type: :string, json_name: "caTtl"
+end
+
+defmodule Schematic.V1.SpiffeConfig do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.SpiffeConfig",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :trust_domain, 1, type: :string, json_name: "trustDomain"
+ field :spire_server_ref, 2, type: :string, json_name: "spireServerRef"
+ field :rotation_policy, 3, type: Schematic.V1.RotationPolicy, json_name: "rotationPolicy"
+end
+
+defmodule Schematic.V1.DidConfig do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.DidConfig",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :consortium_did, 1, type: :string, json_name: "consortiumDid"
+ field :did_method, 2, type: :string, json_name: "didMethod"
+ field :did_document_location, 3, type: :string, json_name: "didDocumentLocation"
+ field :key_rotation_policy, 4, type: :string, json_name: "keyRotationPolicy"
+end
+
+defmodule Schematic.V1.AttestationRequirements do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.AttestationRequirements",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :min_state_hash_freshness, 1, type: :string, json_name: "minStateHashFreshness"
+ field :require_pcr0_through_7, 2, type: :bool, json_name: "requirePcr0Through7"
+end
+
+defmodule Schematic.V1.TrustDomainSpec do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.TrustDomainSpec",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :spiffe, 1, type: Schematic.V1.SpiffeConfig
+ field :did, 2, type: Schematic.V1.DidConfig
+ field :attestation_tier, 3, type: :uint32, json_name: "attestationTier"
+
+ field :attestation_requirements, 4,
+ type: Schematic.V1.AttestationRequirements,
+ json_name: "attestationRequirements"
+end
+
+defmodule Schematic.V1.OidcConfig do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.OidcConfig",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :issuer, 1, type: :string
+ field :jwks_uri, 2, type: :string, json_name: "jwksUri"
+end
+
+defmodule Schematic.V1.MfaPolicy do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.MfaPolicy",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :required, 1, type: :bool
+ field :methods, 2, repeated: true, type: :string
+end
+
+defmodule Schematic.V1.HardwareCredentialPolicy do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.HardwareCredentialPolicy",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :required, 1, type: :bool
+ field :types, 2, repeated: true, type: :string
+end
+
+defmodule Schematic.V1.GroupRoleMapping do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.GroupRoleMapping",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :group, 1, type: :string
+ field :role, 2, type: :string
+end
+
+defmodule Schematic.V1.IdentityAuthority do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.IdentityAuthority",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :provider, 1, type: Schematic.V1.ProviderKind, enum: true
+ field :url, 2, type: :string
+ field :realm, 3, type: :string
+ field :oidc, 4, type: Schematic.V1.OidcConfig
+ field :trust_level, 5, type: Schematic.V1.TrustLevel, json_name: "trustLevel", enum: true
+ field :mfa_policy, 6, type: Schematic.V1.MfaPolicy, json_name: "mfaPolicy"
+
+ field :hardware_credential, 7,
+ type: Schematic.V1.HardwareCredentialPolicy,
+ json_name: "hardwareCredential"
+
+ field :group_to_role_mappings, 8,
+ repeated: true,
+ type: Schematic.V1.GroupRoleMapping,
+ json_name: "groupToRoleMappings"
+end
+
+defmodule Schematic.V1.IdentityClaimMapping do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.IdentityClaimMapping",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :did_from_claim, 1, type: :string, json_name: "didFromClaim"
+ field :roles_from_claim, 2, type: :string, json_name: "rolesFromClaim"
+end
+
+defmodule Schematic.V1.IdentityFederation do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.IdentityFederation",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :upstream_provider, 1,
+ type: Schematic.V1.ProviderKind,
+ json_name: "upstreamProvider",
+ enum: true
+
+ field :upstream_tenant_id, 2, type: :string, json_name: "upstreamTenantId"
+ field :upstream_issuer, 3, type: :string, json_name: "upstreamIssuer"
+ field :broker_client_id, 4, type: :string, json_name: "brokerClientId"
+ field :trust_level, 5, type: Schematic.V1.TrustLevel, json_name: "trustLevel", enum: true
+
+ field :identity_claim_mapping, 6,
+ type: Schematic.V1.IdentityClaimMapping,
+ json_name: "identityClaimMapping"
+end
+
+defmodule Schematic.V1.IdentityAuthorityBundle do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.IdentityAuthorityBundle",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :primary, 1, type: Schematic.V1.IdentityAuthority
+ field :federations, 2, repeated: true, type: Schematic.V1.IdentityFederation
+end
+
+defmodule Schematic.V1.ApprovalChannelSpec do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ApprovalChannelSpec",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :minimum_attestation_level, 1,
+ type: Schematic.V1.AttestationLevel,
+ json_name: "minimumAttestationLevel",
+ enum: true
+end
+
+defmodule Schematic.V1.AllowedChannel do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.AllowedChannel",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :channel, 1, type: Schematic.V1.Channel, enum: true
+ field :custom_adapter_ref, 2, type: :string, json_name: "customAdapterRef"
+end
+
+defmodule Schematic.V1.RatchetPolicy do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.RatchetPolicy",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :tightening_strategy, 1,
+ type: Schematic.V1.CeremonyType,
+ json_name: "tighteningStrategy",
+ enum: true
+
+ field :loosening_strategy, 2,
+ type: Schematic.V1.CeremonyType,
+ json_name: "looseningStrategy",
+ enum: true
+end
+
+defmodule Schematic.V1.EmergencyOverride do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.EmergencyOverride",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :allowed, 1, type: :bool
+ field :override_type, 2, type: Schematic.V1.CeremonyType, json_name: "overrideType", enum: true
+
+ field :post_hoc_ceremony, 3,
+ type: Schematic.V1.CeremonyType,
+ json_name: "postHocCeremony",
+ enum: true
+
+ field :post_hoc_window, 4, type: :string, json_name: "postHocWindow"
+end
+
+defmodule Schematic.V1.CeremonyRequirement do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.CeremonyRequirement",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ oneof :quorum, 0
+
+ field :mutation_type, 1, type: :string, json_name: "mutationType"
+ field :ceremony_type, 2, type: Schematic.V1.CeremonyType, json_name: "ceremonyType", enum: true
+ field :eligible_roles, 3, repeated: true, type: :string, json_name: "eligibleRoles"
+ field :quorum_count, 4, type: :uint32, json_name: "quorumCount", oneof: 0
+ field :quorum_all, 5, type: :bool, json_name: "quorumAll", oneof: 0
+ field :review_window, 6, type: :string, json_name: "reviewWindow"
+ field :approval_channel, 7, type: Schematic.V1.ApprovalChannelSpec, json_name: "approvalChannel"
+
+ field :allowed_channels, 8,
+ repeated: true,
+ type: Schematic.V1.AllowedChannel,
+ json_name: "allowedChannels"
+
+ field :ratchet, 9, type: Schematic.V1.RatchetPolicy
+
+ field :emergency_override, 10,
+ type: Schematic.V1.EmergencyOverride,
+ json_name: "emergencyOverride"
+end
+
+defmodule Schematic.V1.CeremonyRequirementBundle do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.CeremonyRequirementBundle",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :entries, 1, repeated: true, type: Schematic.V1.CeremonyRequirement
+end
+
+defmodule Schematic.V1.Member do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.Member",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :identity, 1, type: :string
+ field :did, 2, type: :string
+
+ field :consortium_role, 3,
+ type: Schematic.V1.ConsortiumRole,
+ json_name: "consortiumRole",
+ enum: true
+
+ field :tier, 4, type: Schematic.V1.TrustTier, enum: true
+ field :enrolled_at, 5, type: Google.Protobuf.Timestamp, json_name: "enrolledAt"
+ field :enrollment_ceremony_ref, 6, type: :string, json_name: "enrollmentCeremonyRef"
+end
+
+defmodule Schematic.V1.Members do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.Members",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :members, 1, repeated: true, type: Schematic.V1.Member
+end
+
+defmodule Schematic.V1.SessionGrantPolicy do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.SessionGrantPolicy",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :allowed_roles, 1, repeated: true, type: :string, json_name: "allowedRoles"
+
+ field :capability_ceiling, 2,
+ type: Schematic.V1.CapabilityCeiling,
+ json_name: "capabilityCeiling",
+ enum: true
+end
+
+defmodule Schematic.V1.FederationPeer do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.FederationPeer",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :did, 2, type: :string
+ field :trust_domain, 3, type: :string, json_name: "trustDomain"
+ field :relationship, 4, type: Schematic.V1.PeerRelationship, enum: true
+ field :shared_accord_ref, 5, type: :string, json_name: "sharedAccordRef"
+ field :mutual_attestation_required, 6, type: :bool, json_name: "mutualAttestationRequired"
+
+ field :session_grant_policy, 7,
+ type: Schematic.V1.SessionGrantPolicy,
+ json_name: "sessionGrantPolicy"
+end
+
+defmodule Schematic.V1.FederationPeers do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.FederationPeers",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :peers, 1, repeated: true, type: Schematic.V1.FederationPeer
+end
+
+defmodule Schematic.V1.ComputeNodeRef do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ComputeNodeRef",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :platform_id, 2, type: :string, json_name: "platformId"
+ field :attestation_tier, 3, type: :uint32, json_name: "attestationTier"
+ field :registry_ref, 4, type: :string, json_name: "registryRef"
+
+ field :assigned_workload_schematics, 5,
+ repeated: true,
+ type: :string,
+ json_name: "assignedWorkloadSchematics"
+end
+
+defmodule Schematic.V1.ClusterRef do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ClusterRef",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :api_server, 2, type: :string, json_name: "apiServer"
+ field :spiffe_trust_domain, 3, type: :string, json_name: "spiffeTrustDomain"
+ field :registry_ref, 4, type: :string, json_name: "registryRef"
+end
+
+defmodule Schematic.V1.BackendRef do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.BackendRef",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :backend_type, 2, type: :string, json_name: "backendType"
+ field :url, 3, type: :string
+ field :keycloak_client_id, 4, type: :string, json_name: "keycloakClientId"
+ field :registry_ref, 5, type: :string, json_name: "registryRef"
+end
+
+defmodule Schematic.V1.CloudProviderRef do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.CloudProviderRef",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :provider, 2, type: :string
+ field :registry_ref, 3, type: :string, json_name: "registryRef"
+end
+
+defmodule Schematic.V1.InfrastructureRefs do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.InfrastructureRefs",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :compute_nodes, 1,
+ repeated: true,
+ type: Schematic.V1.ComputeNodeRef,
+ json_name: "computeNodes"
+
+ field :clusters, 2, repeated: true, type: Schematic.V1.ClusterRef
+ field :backends, 3, repeated: true, type: Schematic.V1.BackendRef
+
+ field :cloud_providers, 4,
+ repeated: true,
+ type: Schematic.V1.CloudProviderRef,
+ json_name: "cloudProviders"
+end
+
+defmodule Schematic.V1.BootChain do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.BootChain",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :pcr0_through_7, 1, repeated: true, type: :string, json_name: "pcr0Through7"
+end
+
+defmodule Schematic.V1.KernelExpectation do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.KernelExpectation",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :version, 1, type: :string
+end
+
+defmodule Schematic.V1.EbpfProgram do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.EbpfProgram",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :role, 1, type: :string
+ field :name, 2, type: :string
+ field :measurement_hash, 3, type: :string, json_name: "measurementHash"
+end
+
+defmodule Schematic.V1.NodeAttestationExpected do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.NodeAttestationExpected",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :boot_chain, 2, type: Schematic.V1.BootChain, json_name: "bootChain"
+ field :kernel, 3, type: Schematic.V1.KernelExpectation
+
+ field :required_ebpf_programs, 4,
+ repeated: true,
+ type: Schematic.V1.EbpfProgram,
+ json_name: "requiredEbpfPrograms"
+end
+
+defmodule Schematic.V1.GlobalAttestationPolicy do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.GlobalAttestationPolicy",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :allow_unexpected_ebpf_programs, 1, type: :bool, json_name: "allowUnexpectedEbpfPrograms"
+
+ field :allow_unexpected_kernel_modules, 2,
+ type: :bool,
+ json_name: "allowUnexpectedKernelModules"
+end
+
+defmodule Schematic.V1.ExpectedAttestationProfile do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ExpectedAttestationProfile",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :per_node, 1,
+ repeated: true,
+ type: Schematic.V1.NodeAttestationExpected,
+ json_name: "perNode"
+
+ field :global_policy, 2, type: Schematic.V1.GlobalAttestationPolicy, json_name: "globalPolicy"
+end
+
+defmodule Schematic.V1.FfcSchematicSignature do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.FfcSchematicSignature",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :signer_did, 1, type: :string, json_name: "signerDid"
+ field :role, 2, type: Schematic.V1.FfcRole, enum: true
+ field :signature, 3, type: :bytes
+ field :signed_at, 4, type: Google.Protobuf.Timestamp, json_name: "signedAt"
+ field :accord_hash, 5, type: :bytes, json_name: "accordHash"
+ field :key_ref, 6, type: :string, json_name: "keyRef"
+end
+
+defmodule Schematic.V1.FfcSchematicArtifact do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.FfcSchematicArtifact",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+ field :parent_hash, 3, type: :string, json_name: "parentHash"
+ field :consortium, 4, type: Schematic.V1.ConsortiumIdentity
+ field :trust_domain, 5, type: Schematic.V1.TrustDomainSpec, json_name: "trustDomain"
+ field :identity, 6, type: Schematic.V1.IdentityAuthorityBundle
+ field :ceremonies, 7, type: Schematic.V1.CeremonyRequirementBundle
+ field :members, 8, type: Schematic.V1.Members
+ field :federation_peers, 9, type: Schematic.V1.FederationPeers, json_name: "federationPeers"
+ field :infrastructure, 10, type: Schematic.V1.InfrastructureRefs
+ field :attestation, 11, type: Schematic.V1.ExpectedAttestationProfile
+ field :signatures, 12, repeated: true, type: Schematic.V1.FfcSchematicSignature
+ field :schematic_hash, 13, type: :bytes, json_name: "schematicHash"
+ field :ceremony_id, 14, type: :bytes, json_name: "ceremonyId"
+ field :accord_hash, 15, type: :bytes, json_name: "accordHash"
+ field :created_at, 16, type: Google.Protobuf.Timestamp, json_name: "createdAt"
+ field :status, 17, type: Schematic.V1.FfcSchematicStatus, enum: true
+end
+
+defmodule Schematic.V1.FfcSchematicFile do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.FfcSchematicFile",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :path, 1, type: :string
+ field :content, 2, type: :bytes
+end
+
+defmodule Schematic.V1.CreateFfcSchematicRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.CreateFfcSchematicRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+ field :manifest_yaml, 3, type: :bytes, json_name: "manifestYaml"
+ field :files, 4, repeated: true, type: Schematic.V1.FfcSchematicFile
+end
+
+defmodule Schematic.V1.CreateFfcSchematicResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.CreateFfcSchematicResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+ field :tree_hash, 3, type: :string, json_name: "treeHash"
+ field :status, 4, type: Schematic.V1.FfcSchematicStatus, enum: true
+end
+
+defmodule Schematic.V1.GetFfcSchematicRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.GetFfcSchematicRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+end
+
+defmodule Schematic.V1.GetFfcSchematicResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.GetFfcSchematicResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :artifact, 1, type: Schematic.V1.FfcSchematicArtifact
+end
+
+defmodule Schematic.V1.ListFfcSchematicVersionsRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ListFfcSchematicVersionsRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+end
+
+defmodule Schematic.V1.FfcVersionInfo do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.FfcVersionInfo",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :version, 1, type: :string
+ field :parent_hash, 2, type: :string, json_name: "parentHash"
+ field :status, 3, type: Schematic.V1.FfcSchematicStatus, enum: true
+ field :created_at, 4, type: Google.Protobuf.Timestamp, json_name: "createdAt"
+end
+
+defmodule Schematic.V1.ListFfcSchematicVersionsResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ListFfcSchematicVersionsResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :versions, 1, repeated: true, type: Schematic.V1.FfcVersionInfo
+end
+
+defmodule Schematic.V1.UpdateFfcSchematicDraftRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.UpdateFfcSchematicDraftRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+ field :files, 3, repeated: true, type: Schematic.V1.FfcSchematicFile
+end
+
+defmodule Schematic.V1.UpdateFfcSchematicDraftResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.UpdateFfcSchematicDraftResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :tree_hash, 1, type: :string, json_name: "treeHash"
+end
+
+defmodule Schematic.V1.ValidateFfcSchematicRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ValidateFfcSchematicRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+end
+
+defmodule Schematic.V1.FfcValidationResult do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.FfcValidationResult",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :validator, 1, type: :string
+ field :passed, 2, type: :bool
+ field :message, 3, type: :string
+ field :severity, 4, type: :string
+end
+
+defmodule Schematic.V1.ValidateFfcSchematicResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ValidateFfcSchematicResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :valid, 1, type: :bool
+ field :results, 2, repeated: true, type: Schematic.V1.FfcValidationResult
+end
+
+defmodule Schematic.V1.ApproveFfcSchematicRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ApproveFfcSchematicRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+ field :signer_did, 3, type: :string, json_name: "signerDid"
+ field :role, 4, type: Schematic.V1.FfcRole, enum: true
+ field :signature, 5, type: :bytes
+ field :accord_hash, 6, type: :bytes, json_name: "accordHash"
+ field :key_ref, 7, type: :string, json_name: "keyRef"
+end
+
+defmodule Schematic.V1.ApproveFfcSchematicResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ApproveFfcSchematicResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :accepted, 1, type: :bool
+ field :message, 2, type: :string
+end
+
+defmodule Schematic.V1.PublishFfcSchematicRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.PublishFfcSchematicRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+end
+
+defmodule Schematic.V1.PublishFfcSchematicResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.PublishFfcSchematicResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :schematic_hash, 1, type: :bytes, json_name: "schematicHash"
+ field :ceremony_id, 2, type: :bytes, json_name: "ceremonyId"
+ field :status, 3, type: Schematic.V1.FfcSchematicStatus, enum: true
+end
+
+defmodule Schematic.V1.RealizeFfcSchematicRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.RealizeFfcSchematicRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+end
+
+defmodule Schematic.V1.RealizeFfcSchematicResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.RealizeFfcSchematicResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :realization_id, 1, type: :string, json_name: "realizationId"
+ field :status, 2, type: Schematic.V1.FfcSchematicStatus, enum: true
+end
+
+defmodule Schematic.V1.GetRealizationStatusRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.GetRealizationStatusRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :realization_id, 1, type: :string, json_name: "realizationId"
+end
+
+defmodule Schematic.V1.FfcSectionRealizationStatus do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.FfcSectionRealizationStatus",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :section_name, 1, type: :string, json_name: "sectionName"
+ field :status, 2, type: :string
+ field :message, 3, type: :string
+end
+
+defmodule Schematic.V1.GetRealizationStatusResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.GetRealizationStatusResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :overall_status, 1,
+ type: Schematic.V1.FfcSchematicStatus,
+ json_name: "overallStatus",
+ enum: true
+
+ field :per_section, 2,
+ repeated: true,
+ type: Schematic.V1.FfcSectionRealizationStatus,
+ json_name: "perSection"
+end
+
+defmodule Schematic.V1.CreateNextFfcVersionRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.CreateNextFfcVersionRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :from_version, 2, type: :string, json_name: "fromVersion"
+ field :bump, 3, type: :string
+end
+
+defmodule Schematic.V1.CreateNextFfcVersionResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.CreateNextFfcVersionResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :new_version, 1, type: :string, json_name: "newVersion"
+ field :parent_hash, 2, type: :string, json_name: "parentHash"
+end
+
+defmodule Schematic.V1.FfcSchematicService.Service do
+ @moduledoc false
+
+ use GRPC.Service, name: "schematic.v1.FfcSchematicService", protoc_gen_elixir_version: "0.16.0"
+
+ rpc :CreateFfcSchematic,
+ Schematic.V1.CreateFfcSchematicRequest,
+ Schematic.V1.CreateFfcSchematicResponse
+
+ rpc :GetFfcSchematic, Schematic.V1.GetFfcSchematicRequest, Schematic.V1.GetFfcSchematicResponse
+
+ rpc :ListFfcSchematicVersions,
+ Schematic.V1.ListFfcSchematicVersionsRequest,
+ Schematic.V1.ListFfcSchematicVersionsResponse
+
+ rpc :UpdateFfcSchematicDraft,
+ Schematic.V1.UpdateFfcSchematicDraftRequest,
+ Schematic.V1.UpdateFfcSchematicDraftResponse
+
+ rpc :ValidateFfcSchematic,
+ Schematic.V1.ValidateFfcSchematicRequest,
+ Schematic.V1.ValidateFfcSchematicResponse
+
+ rpc :ApproveFfcSchematic,
+ Schematic.V1.ApproveFfcSchematicRequest,
+ Schematic.V1.ApproveFfcSchematicResponse
+
+ rpc :PublishFfcSchematic,
+ Schematic.V1.PublishFfcSchematicRequest,
+ Schematic.V1.PublishFfcSchematicResponse
+
+ rpc :RealizeFfcSchematic,
+ Schematic.V1.RealizeFfcSchematicRequest,
+ Schematic.V1.RealizeFfcSchematicResponse
+
+ rpc :GetRealizationStatus,
+ Schematic.V1.GetRealizationStatusRequest,
+ Schematic.V1.GetRealizationStatusResponse
+
+ rpc :CreateNextVersion,
+ Schematic.V1.CreateNextFfcVersionRequest,
+ Schematic.V1.CreateNextFfcVersionResponse
+end
+
+defmodule Schematic.V1.FfcSchematicService.Stub do
+ @moduledoc false
+
+ use GRPC.Stub, service: Schematic.V1.FfcSchematicService.Service
+end
diff --git a/apps/guildhall_orchestrator/lib/guildhall/orchestrator/proto/schematic/v1/schematics.pb.ex b/apps/guildhall_orchestrator/lib/guildhall/orchestrator/proto/schematic/v1/schematics.pb.ex
new file mode 100644
index 0000000..8bc0c93
--- /dev/null
+++ b/apps/guildhall_orchestrator/lib/guildhall/orchestrator/proto/schematic/v1/schematics.pb.ex
@@ -0,0 +1,452 @@
+defmodule Schematic.V1.CreateSchematicRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.CreateSchematicRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+ field :manifest_yaml, 3, type: :bytes, json_name: "manifestYaml"
+ field :files, 4, repeated: true, type: Schematic.V1.SchematicFile
+end
+
+defmodule Schematic.V1.SchematicFile do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.SchematicFile",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :path, 1, type: :string
+ field :content, 2, type: :bytes
+end
+
+defmodule Schematic.V1.CreateSchematicResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.CreateSchematicResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+ field :tree_hash, 3, type: :string, json_name: "treeHash"
+ field :status, 4, type: :string
+end
+
+defmodule Schematic.V1.GetSchematicRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.GetSchematicRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+end
+
+defmodule Schematic.V1.GetSchematicResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.GetSchematicResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+ field :tree_hash, 3, type: :string, json_name: "treeHash"
+ field :status, 4, type: :string
+ field :parent_hash, 5, type: :string, json_name: "parentHash"
+ field :stakeholders, 6, repeated: true, type: Schematic.V1.StakeholderInfo
+ field :created_at, 7, type: Google.Protobuf.Timestamp, json_name: "createdAt"
+end
+
+defmodule Schematic.V1.StakeholderInfo do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.StakeholderInfo",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :role, 1, type: :string
+ field :identity, 2, type: :string
+ field :required, 3, type: :bool
+end
+
+defmodule Schematic.V1.ListVersionsRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ListVersionsRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+end
+
+defmodule Schematic.V1.ListVersionsResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ListVersionsResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :versions, 1, repeated: true, type: Schematic.V1.VersionInfo
+end
+
+defmodule Schematic.V1.VersionInfo do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.VersionInfo",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :version, 1, type: :string
+ field :tree_hash, 2, type: :string, json_name: "treeHash"
+ field :status, 3, type: :string
+ field :parent_hash, 4, type: :string, json_name: "parentHash"
+ field :created_at, 5, type: Google.Protobuf.Timestamp, json_name: "createdAt"
+end
+
+defmodule Schematic.V1.UpdateSchematicRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.UpdateSchematicRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+ field :files, 3, repeated: true, type: Schematic.V1.SchematicFile
+end
+
+defmodule Schematic.V1.UpdateSchematicResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.UpdateSchematicResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :tree_hash, 1, type: :string, json_name: "treeHash"
+ field :status, 2, type: :string
+end
+
+defmodule Schematic.V1.ValidateSchematicRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ValidateSchematicRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+end
+
+defmodule Schematic.V1.ValidateSchematicResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ValidateSchematicResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :valid, 1, type: :bool
+ field :error_count, 2, type: :int32, json_name: "errorCount"
+ field :warning_count, 3, type: :int32, json_name: "warningCount"
+ field :results, 4, repeated: true, type: Schematic.V1.ValidationResultProto
+end
+
+defmodule Schematic.V1.ValidationResultProto do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ValidationResultProto",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :validator, 1, type: :string
+ field :passed, 2, type: :bool
+ field :message, 3, type: :string
+ field :severity, 4, type: :string
+end
+
+defmodule Schematic.V1.ApproveSchematicRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ApproveSchematicRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+ field :role, 3, type: :string
+ field :identity, 4, type: :string
+ field :tree_hash, 5, type: :string, json_name: "treeHash"
+ field :comment, 6, type: :string
+end
+
+defmodule Schematic.V1.ApproveSchematicResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ApproveSchematicResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :accepted, 1, type: :bool
+ field :approval_status, 2, type: :string, json_name: "approvalStatus"
+ field :message, 3, type: :string
+end
+
+defmodule Schematic.V1.GetApprovalStatusRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.GetApprovalStatusRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+end
+
+defmodule Schematic.V1.GetApprovalStatusResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.GetApprovalStatusResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :status, 1, type: :string
+ field :approved_roles, 2, repeated: true, type: :string, json_name: "approvedRoles"
+ field :remaining_roles, 3, repeated: true, type: :string, json_name: "remainingRoles"
+ field :approvals, 4, repeated: true, type: Schematic.V1.ApprovalInfo
+end
+
+defmodule Schematic.V1.ApprovalInfo do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ApprovalInfo",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :role, 1, type: :string
+ field :identity, 2, type: :string
+ field :approved_at, 3, type: Google.Protobuf.Timestamp, json_name: "approvedAt"
+ field :comment, 4, type: :string
+end
+
+defmodule Schematic.V1.PublishSchematicRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.PublishSchematicRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+end
+
+defmodule Schematic.V1.PublishSchematicResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.PublishSchematicResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :tree_hash, 1, type: :string, json_name: "treeHash"
+ field :status, 2, type: :string
+end
+
+defmodule Schematic.V1.CreateNextVersionRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.CreateNextVersionRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :from_version, 2, type: :string, json_name: "fromVersion"
+ field :bump, 3, type: :string
+end
+
+defmodule Schematic.V1.CreateNextVersionResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.CreateNextVersionResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :version, 1, type: :string
+ field :tree_hash, 2, type: :string, json_name: "treeHash"
+ field :parent_hash, 3, type: :string, json_name: "parentHash"
+ field :status, 4, type: :string
+end
+
+defmodule Schematic.V1.ForkSchematicRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ForkSchematicRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :source_name, 1, type: :string, json_name: "sourceName"
+ field :source_version, 2, type: :string, json_name: "sourceVersion"
+ field :new_name, 3, type: :string, json_name: "newName"
+ field :new_version, 4, type: :string, json_name: "newVersion"
+ field :operations, 5, repeated: true, type: Schematic.V1.TemplateOperation
+end
+
+defmodule Schematic.V1.TemplateOperation do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.TemplateOperation",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :op_type, 1, type: :string, json_name: "opType"
+ field :path, 2, type: :string
+ field :content, 3, type: :bytes
+end
+
+defmodule Schematic.V1.ForkSchematicResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.ForkSchematicResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :name, 1, type: :string
+ field :version, 2, type: :string
+ field :tree_hash, 3, type: :string, json_name: "treeHash"
+ field :status, 4, type: :string
+end
+
+defmodule Schematic.V1.CreateDeploymentBindingRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.CreateDeploymentBindingRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :schematic_name, 1, type: :string, json_name: "schematicName"
+ field :schematic_version, 2, type: :string, json_name: "schematicVersion"
+ field :pipeline_name, 3, type: :string, json_name: "pipelineName"
+ field :target_env, 4, type: :string, json_name: "targetEnv"
+end
+
+defmodule Schematic.V1.CreateDeploymentBindingResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.CreateDeploymentBindingResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :binding_id, 1, type: :string, json_name: "bindingId"
+ field :status, 2, type: :string
+end
+
+defmodule Schematic.V1.GetDeploymentBindingRequest do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.GetDeploymentBindingRequest",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :binding_id, 1, type: :string, json_name: "bindingId"
+end
+
+defmodule Schematic.V1.GetDeploymentBindingResponse do
+ @moduledoc false
+
+ use Protobuf,
+ full_name: "schematic.v1.GetDeploymentBindingResponse",
+ protoc_gen_elixir_version: "0.16.0",
+ syntax: :proto3
+
+ field :binding_id, 1, type: :string, json_name: "bindingId"
+ field :schematic_name, 2, type: :string, json_name: "schematicName"
+ field :schematic_version, 3, type: :string, json_name: "schematicVersion"
+ field :tree_hash, 4, type: :string, json_name: "treeHash"
+ field :pipeline_name, 5, type: :string, json_name: "pipelineName"
+ field :target_env, 6, type: :string, json_name: "targetEnv"
+ field :status, 7, type: :string
+ field :run_id, 8, type: :string, json_name: "runId"
+ field :created_at, 9, type: Google.Protobuf.Timestamp, json_name: "createdAt"
+ field :deployed_at, 10, type: Google.Protobuf.Timestamp, json_name: "deployedAt"
+end
+
+defmodule Schematic.V1.SchematicsService.Service do
+ @moduledoc false
+
+ use GRPC.Service, name: "schematic.v1.SchematicsService", protoc_gen_elixir_version: "0.16.0"
+
+ rpc :CreateSchematic, Schematic.V1.CreateSchematicRequest, Schematic.V1.CreateSchematicResponse
+
+ rpc :GetSchematic, Schematic.V1.GetSchematicRequest, Schematic.V1.GetSchematicResponse
+
+ rpc :ListVersions, Schematic.V1.ListVersionsRequest, Schematic.V1.ListVersionsResponse
+
+ rpc :UpdateSchematic, Schematic.V1.UpdateSchematicRequest, Schematic.V1.UpdateSchematicResponse
+
+ rpc :ValidateSchematic,
+ Schematic.V1.ValidateSchematicRequest,
+ Schematic.V1.ValidateSchematicResponse
+
+ rpc :ApproveSchematic,
+ Schematic.V1.ApproveSchematicRequest,
+ Schematic.V1.ApproveSchematicResponse
+
+ rpc :GetApprovalStatus,
+ Schematic.V1.GetApprovalStatusRequest,
+ Schematic.V1.GetApprovalStatusResponse
+
+ rpc :PublishSchematic,
+ Schematic.V1.PublishSchematicRequest,
+ Schematic.V1.PublishSchematicResponse
+
+ rpc :CreateNextVersion,
+ Schematic.V1.CreateNextVersionRequest,
+ Schematic.V1.CreateNextVersionResponse
+
+ rpc :ForkSchematic, Schematic.V1.ForkSchematicRequest, Schematic.V1.ForkSchematicResponse
+
+ rpc :CreateDeploymentBinding,
+ Schematic.V1.CreateDeploymentBindingRequest,
+ Schematic.V1.CreateDeploymentBindingResponse
+
+ rpc :GetDeploymentBinding,
+ Schematic.V1.GetDeploymentBindingRequest,
+ Schematic.V1.GetDeploymentBindingResponse
+end
+
+defmodule Schematic.V1.SchematicsService.Stub do
+ @moduledoc false
+
+ use GRPC.Stub, service: Schematic.V1.SchematicsService.Service
+end
diff --git a/apps/guildhall_orchestrator/lib/guildhall/orchestrator/realization_poller.ex b/apps/guildhall_orchestrator/lib/guildhall/orchestrator/realization_poller.ex
new file mode 100644
index 0000000..938843d
--- /dev/null
+++ b/apps/guildhall_orchestrator/lib/guildhall/orchestrator/realization_poller.ex
@@ -0,0 +1,91 @@
+defmodule Guildhall.Orchestrator.RealizationPoller do
+ use GenServer
+ require Logger
+
+ alias Guildhall.Orchestrator.SchematicClient
+
+ @poll_interval_ms 5_000
+
+ def start_link(opts), do: GenServer.start_link(__MODULE__, opts, name: __MODULE__)
+
+ def watch(realization_id, guild_slug) do
+ GenServer.cast(__MODULE__, {:watch, realization_id, guild_slug})
+ end
+
+ def unwatch(realization_id) do
+ GenServer.cast(__MODULE__, {:unwatch, realization_id})
+ end
+
+ @impl true
+ def init(_opts) do
+ Logger.info("RealizationPoller started")
+ schedule_poll()
+ {:ok, %{watches: %{}}}
+ end
+
+ @impl true
+ def handle_cast({:watch, realization_id, guild_slug}, state) do
+ {:noreply, put_in(state, [:watches, realization_id], guild_slug)}
+ end
+
+ @impl true
+ def handle_cast({:unwatch, realization_id}, state) do
+ {:noreply, %{state | watches: Map.delete(state.watches, realization_id)}}
+ end
+
+ @impl true
+ def handle_info(:poll, %{watches: watches} = state) when map_size(watches) == 0 do
+ schedule_poll()
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_info(:poll, state) do
+ new_watches =
+ Enum.reduce(state.watches, state.watches, fn {realization_id, guild_slug}, acc ->
+ case SchematicClient.get_realization_status(realization_id) do
+ {:ok, response} ->
+ snapshot = build_snapshot(response)
+
+ Phoenix.PubSub.broadcast(
+ Guildhall.PubSub,
+ "realization:#{guild_slug}",
+ {:realization_update, snapshot}
+ )
+
+ if terminal_status?(response.overall_status) do
+ Map.delete(acc, realization_id)
+ else
+ acc
+ end
+
+ {:error, reason} ->
+ Logger.warning("Realization poll failed for #{realization_id}: #{inspect(reason)}")
+ acc
+ end
+ end)
+
+ schedule_poll()
+ {:noreply, %{state | watches: new_watches}}
+ end
+
+ @impl true
+ def handle_info(_msg, state), do: {:noreply, state}
+
+ defp schedule_poll, do: Process.send_after(self(), :poll, @poll_interval_ms)
+
+ defp build_snapshot(response) do
+ %{
+ overall_status: to_string(response.overall_status),
+ sections:
+ Enum.map(response.per_section, fn s ->
+ %{name: s.section_name, status: s.status, message: s.message}
+ end)
+ }
+ end
+
+ defp terminal_status?(:FFC_SCHEMATIC_STATUS_REALIZED), do: true
+ defp terminal_status?(:FFC_SCHEMATIC_STATUS_ARCHIVED), do: true
+ defp terminal_status?(:FFC_SCHEMATIC_STATUS_WITHDRAWN), do: true
+ defp terminal_status?(_), do: false
+end
diff --git a/apps/guildhall_orchestrator/lib/guildhall/orchestrator/schematic_client.ex b/apps/guildhall_orchestrator/lib/guildhall/orchestrator/schematic_client.ex
new file mode 100644
index 0000000..2b7aaf8
--- /dev/null
+++ b/apps/guildhall_orchestrator/lib/guildhall/orchestrator/schematic_client.ex
@@ -0,0 +1,98 @@
+defmodule Guildhall.Orchestrator.SchematicClient do
+ @moduledoc false
+
+ alias Schematic.V1.{
+ SchematicsService.Stub,
+ ForkSchematicRequest,
+ TemplateOperation,
+ CreateDeploymentBindingRequest,
+ GetDeploymentBindingRequest
+ }
+
+ alias Schematic.V1.RealizeFfcSchematicRequest
+ alias Schematic.V1.GetRealizationStatusRequest
+
+ def fork_schematic(source_name, source_version, new_name, new_version, operations) do
+ ops =
+ Enum.map(operations, fn op ->
+ %TemplateOperation{
+ op_type: op[:op_type] || "replace",
+ path: op[:path],
+ content: op[:content] || ""
+ }
+ end)
+
+ request = %ForkSchematicRequest{
+ source_name: source_name,
+ source_version: source_version,
+ new_name: new_name,
+ new_version: new_version,
+ operations: ops
+ }
+
+ with {:ok, channel} <- connect_schematic(),
+ {:ok, response} <- Stub.fork_schematic(channel, request) do
+ GRPC.Stub.disconnect(channel)
+ {:ok, response}
+ end
+ end
+
+ def create_deployment_binding(schematic_name, version, pipeline_name, target_env) do
+ request = %CreateDeploymentBindingRequest{
+ schematic_name: schematic_name,
+ schematic_version: version,
+ pipeline_name: pipeline_name,
+ target_env: target_env
+ }
+
+ with {:ok, channel} <- connect_schematic(),
+ {:ok, response} <- Stub.create_deployment_binding(channel, request) do
+ GRPC.Stub.disconnect(channel)
+ {:ok, response}
+ end
+ end
+
+ def get_deployment_binding(binding_id) do
+ request = %GetDeploymentBindingRequest{binding_id: binding_id}
+
+ with {:ok, channel} <- connect_schematic(),
+ {:ok, response} <- Stub.get_deployment_binding(channel, request) do
+ GRPC.Stub.disconnect(channel)
+ {:ok, response}
+ end
+ end
+
+ def realize_ffc_schematic(name, version) do
+ request = %RealizeFfcSchematicRequest{name: name, version: version}
+
+ with {:ok, channel} <- connect_ffc(),
+ {:ok, response} <- Schematic.V1.FfcSchematicService.Stub.realize_ffc_schematic(channel, request) do
+ GRPC.Stub.disconnect(channel)
+ {:ok, response}
+ end
+ end
+
+ def get_realization_status(realization_id) do
+ request = %GetRealizationStatusRequest{realization_id: realization_id}
+
+ with {:ok, channel} <- connect_ffc(),
+ {:ok, response} <- Schematic.V1.FfcSchematicService.Stub.get_realization_status(channel, request) do
+ GRPC.Stub.disconnect(channel)
+ {:ok, response}
+ end
+ end
+
+ defp connect_schematic do
+ url =
+ Application.get_env(:guildhall_orchestrator, :schematic_service_url, "localhost:9091")
+
+ GRPC.Stub.connect(url)
+ end
+
+ defp connect_ffc do
+ url =
+ Application.get_env(:guildhall_orchestrator, :ffc_schematic_service_url, "localhost:9091")
+
+ GRPC.Stub.connect(url)
+ end
+end
diff --git a/apps/guildhall_orchestrator/lib/guildhall/orchestrator/schematic_template.ex b/apps/guildhall_orchestrator/lib/guildhall/orchestrator/schematic_template.ex
new file mode 100644
index 0000000..bb9f8c3
--- /dev/null
+++ b/apps/guildhall_orchestrator/lib/guildhall/orchestrator/schematic_template.ex
@@ -0,0 +1,70 @@
+defmodule Guildhall.Orchestrator.SchematicTemplate do
+ @moduledoc false
+
+ @type_to_template %{
+ "msp" => "msp-founding.toml",
+ "isv" => "isv-founding.toml",
+ "nsp" => "nsp-founding.toml"
+ }
+
+ def load_template(guild_type) do
+ filename = Map.fetch!(@type_to_template, guild_type)
+ path = Path.join(template_dir(), filename)
+
+ case Toml.decode_file(path) do
+ {:ok, data} -> {:ok, data}
+ {:error, reason} -> {:error, {:template_load_failed, reason}}
+ end
+ end
+
+ def render_template(template, params) when is_map(template) and is_map(params) do
+ deep_substitute(template, params)
+ end
+
+ def to_fork_operations(rendered_template) do
+ rendered_template
+ |> Map.drop(["meta"])
+ |> Enum.flat_map(fn {section, content} ->
+ yaml = to_yaml_string(content)
+
+ [
+ %{
+ op_type: "replace",
+ path: "#{section}.yaml",
+ content: yaml
+ }
+ ]
+ end)
+ end
+
+ def source_schematic(template) do
+ meta = Map.get(template, "meta", %{})
+ {Map.get(meta, "source_schematic", ""), Map.get(meta, "source_version", "1.0.0")}
+ end
+
+ defp deep_substitute(value, params) when is_binary(value) do
+ Enum.reduce(params, value, fn {key, val}, acc ->
+ String.replace(acc, "{{#{key}}}", to_string(val))
+ end)
+ end
+
+ defp deep_substitute(value, params) when is_map(value) do
+ Map.new(value, fn {k, v} -> {k, deep_substitute(v, params)} end)
+ end
+
+ defp deep_substitute(value, params) when is_list(value) do
+ Enum.map(value, &deep_substitute(&1, params))
+ end
+
+ defp deep_substitute(value, _params), do: value
+
+ defp to_yaml_string(map) when is_map(map) do
+ Jason.encode!(map)
+ end
+
+ defp to_yaml_string(other), do: to_string(other)
+
+ defp template_dir do
+ Application.app_dir(:guildhall_orchestrator, "priv/schematic_templates")
+ end
+end
diff --git a/apps/guildhall_orchestrator/mix.exs b/apps/guildhall_orchestrator/mix.exs
index 9860875..4b522e3 100644
--- a/apps/guildhall_orchestrator/mix.exs
+++ b/apps/guildhall_orchestrator/mix.exs
@@ -28,7 +28,11 @@ defmodule Guildhall.Orchestrator.MixProject do
[
{:guildhall_ops_db, in_umbrella: true},
{:phoenix_pubsub, "~> 2.1"},
- {:jason, "~> 1.4"}
+ {:jason, "~> 1.4"},
+ {:grpc, "~> 0.9"},
+ {:protobuf, "~> 0.13"},
+ {:google_protos, "~> 0.4"},
+ {:toml, "~> 0.7"}
]
end
end
diff --git a/apps/guildhall_orchestrator/priv/schematic_templates/isv-founding.toml b/apps/guildhall_orchestrator/priv/schematic_templates/isv-founding.toml
new file mode 100644
index 0000000..5dd8340
--- /dev/null
+++ b/apps/guildhall_orchestrator/priv/schematic_templates/isv-founding.toml
@@ -0,0 +1,36 @@
+[meta]
+template_name = "isv-founding"
+description = "Independent Software Vendor founding schematic"
+source_schematic = "guildhouse-isv-base"
+source_version = "1.0.0"
+
+[consortium]
+realm = "{{guild_slug}}"
+description = "{{guild_name}} ISV Consortium"
+
+[trust_domain]
+spiffe_trust_domain = "{{trust_domain}}"
+attestation_tier = 1
+
+[identity_authority]
+provider = "keycloak"
+url = "https://auth.guildhouse.dev"
+realm = "{{guild_slug}}"
+trust_level = "federated"
+mfa_required = false
+
+[ceremonies.code_change]
+type = "single_approval"
+eligible_roles = ["master", "journeyman"]
+quorum = 1
+
+[ceremonies.governance_change]
+type = "single_approval"
+eligible_roles = ["master"]
+quorum = 1
+
+[members]
+initial_roles = ["master"]
+
+[infrastructure]
+compute_attestation_tier = 1
diff --git a/apps/guildhall_orchestrator/priv/schematic_templates/msp-founding.toml b/apps/guildhall_orchestrator/priv/schematic_templates/msp-founding.toml
new file mode 100644
index 0000000..7671b6c
--- /dev/null
+++ b/apps/guildhall_orchestrator/priv/schematic_templates/msp-founding.toml
@@ -0,0 +1,36 @@
+[meta]
+template_name = "msp-founding"
+description = "Managed Service Provider founding schematic"
+source_schematic = "guildhouse-msp-base"
+source_version = "1.0.0"
+
+[consortium]
+realm = "{{guild_slug}}"
+description = "{{guild_name}} MSP Consortium"
+
+[trust_domain]
+spiffe_trust_domain = "{{trust_domain}}"
+attestation_tier = 2
+
+[identity_authority]
+provider = "keycloak"
+url = "https://auth.guildhouse.dev"
+realm = "{{guild_slug}}"
+trust_level = "federated"
+mfa_required = true
+
+[ceremonies.code_change]
+type = "single_approval"
+eligible_roles = ["master", "journeyman"]
+quorum = 1
+
+[ceremonies.governance_change]
+type = "single_approval"
+eligible_roles = ["master"]
+quorum = 1
+
+[members]
+initial_roles = ["master"]
+
+[infrastructure]
+compute_attestation_tier = 2
diff --git a/apps/guildhall_orchestrator/priv/schematic_templates/nsp-founding.toml b/apps/guildhall_orchestrator/priv/schematic_templates/nsp-founding.toml
new file mode 100644
index 0000000..a08628a
--- /dev/null
+++ b/apps/guildhall_orchestrator/priv/schematic_templates/nsp-founding.toml
@@ -0,0 +1,39 @@
+[meta]
+template_name = "nsp-founding"
+description = "Network Service Provider founding schematic"
+source_schematic = "guildhouse-nsp-base"
+source_version = "1.0.0"
+
+[consortium]
+realm = "{{guild_slug}}"
+description = "{{guild_name}} NSP Consortium"
+
+[trust_domain]
+spiffe_trust_domain = "{{trust_domain}}"
+attestation_tier = 3
+
+[identity_authority]
+provider = "keycloak"
+url = "https://auth.guildhouse.dev"
+realm = "{{guild_slug}}"
+trust_level = "federated"
+mfa_required = true
+hardware_credential_required = true
+
+[ceremonies.code_change]
+type = "single_approval"
+eligible_roles = ["master", "journeyman"]
+quorum = 1
+
+[ceremonies.governance_change]
+type = "multi_party"
+eligible_roles = ["master"]
+quorum = 2
+
+[members]
+initial_roles = ["master"]
+
+[infrastructure]
+compute_attestation_tier = 3
+wireguard_tunnel = true
+vpp_dataplane = true
diff --git a/apps/guildhall_web/lib/guildhall_web/application.ex b/apps/guildhall_web/lib/guildhall_web/application.ex
index f192d36..7701c05 100644
--- a/apps/guildhall_web/lib/guildhall_web/application.ex
+++ b/apps/guildhall_web/lib/guildhall_web/application.ex
@@ -7,15 +7,31 @@ defmodule Guildhall.Application do
@impl true
def start(_type, _args) do
- children = [
- GuildhallWeb.Telemetry,
- {DNSCluster, query: Application.get_env(:guildhall_web, :dns_cluster_query) || :ignore},
- {Phoenix.PubSub, name: Guildhall.PubSub},
- # Start a worker by calling: Guildhall.Worker.start_link(arg)
- # {Guildhall.Worker, arg},
- # Start to serve requests, typically the last entry
- GuildhallWeb.Endpoint
- ]
+ oidc_children =
+ case Application.get_env(:guildhall_web, :oidc) do
+ nil ->
+ []
+
+ config ->
+ [
+ {Oidcc.ProviderConfiguration.Worker,
+ %{
+ issuer: config[:issuer],
+ name: GuildhallWeb.OidcProvider
+ }}
+ ]
+ end
+
+ children =
+ [
+ GuildhallWeb.Telemetry,
+ {DNSCluster, query: Application.get_env(:guildhall_web, :dns_cluster_query) || :ignore},
+ {Phoenix.PubSub, name: Guildhall.PubSub}
+ ] ++
+ oidc_children ++
+ [
+ GuildhallWeb.Endpoint
+ ]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
diff --git a/apps/guildhall_web/lib/guildhall_web_web/controllers/auth_controller.ex b/apps/guildhall_web/lib/guildhall_web_web/controllers/auth_controller.ex
new file mode 100644
index 0000000..ac867e8
--- /dev/null
+++ b/apps/guildhall_web/lib/guildhall_web_web/controllers/auth_controller.ex
@@ -0,0 +1,96 @@
+defmodule GuildhallWeb.AuthController do
+ use GuildhallWeb, :controller
+
+ @provider_name GuildhallWeb.OidcProvider
+
+ def login(conn, _params) do
+ oidc_config = Application.fetch_env!(:guildhall_web, :oidc)
+ client_id = oidc_config[:client_id]
+ client_secret = oidc_config[:client_secret]
+ redirect_uri = oidc_config[:redirect_uri]
+
+ nonce = :crypto.strong_rand_bytes(16) |> Base.url_encode64(padding: false)
+ state = :crypto.strong_rand_bytes(16) |> Base.url_encode64(padding: false)
+
+ case Oidcc.create_redirect_url(@provider_name, client_id, client_secret, %{
+ redirect_uri: redirect_uri,
+ scopes: ["openid", "profile", "email"],
+ state: state,
+ nonce: nonce
+ }) do
+ {:ok, url} ->
+ conn
+ |> put_session(:oidc_state, state)
+ |> put_session(:oidc_nonce, nonce)
+ |> redirect(external: url)
+
+ {:error, reason} ->
+ conn
+ |> put_flash(:error, "Failed to initiate login: #{inspect(reason)}")
+ |> redirect(to: "/")
+ end
+ end
+
+ def callback(conn, %{"code" => code, "state" => state}) do
+ saved_state = get_session(conn, :oidc_state)
+
+ if state != saved_state do
+ conn
+ |> put_flash(:error, "Invalid state parameter.")
+ |> redirect(to: "/")
+ else
+ oidc_config = Application.fetch_env!(:guildhall_web, :oidc)
+ client_id = oidc_config[:client_id]
+ client_secret = oidc_config[:client_secret]
+ redirect_uri = oidc_config[:redirect_uri]
+ nonce = get_session(conn, :oidc_nonce)
+
+ case Oidcc.retrieve_token(code, @provider_name, client_id, client_secret, %{
+ redirect_uri: redirect_uri,
+ nonce: nonce
+ }) do
+ {:ok, token} ->
+ claims = extract_claims(token)
+
+ preferred_username =
+ claims["preferred_username"] || claims["email"] || claims["sub"]
+
+ current_user = %{
+ "sub" => claims["sub"],
+ "email" => claims["email"],
+ "name" => claims["name"] || preferred_username,
+ "preferred_username" => preferred_username,
+ "did" => "did:web:guildhouse.dev:user:#{preferred_username}"
+ }
+
+ conn
+ |> delete_session(:oidc_state)
+ |> delete_session(:oidc_nonce)
+ |> put_session(:current_user, current_user)
+ |> put_flash(:info, "Welcome, #{current_user["name"]}.")
+ |> redirect(to: "/")
+
+ {:error, reason} ->
+ conn
+ |> put_flash(:error, "Authentication failed: #{inspect(reason)}")
+ |> redirect(to: "/")
+ end
+ end
+ end
+
+ def callback(conn, _params) do
+ conn
+ |> put_flash(:error, "Missing authorization code.")
+ |> redirect(to: "/")
+ end
+
+ def logout(conn, _params) do
+ conn
+ |> configure_session(drop: true)
+ |> put_flash(:info, "Signed out.")
+ |> redirect(to: "/")
+ end
+
+ defp extract_claims(%Oidcc.Token{id: %Oidcc.Token.Id{claims: claims}}), do: claims
+ defp extract_claims(_), do: %{}
+end
diff --git a/apps/guildhall_web/lib/guildhall_web_web/live/auth_hooks.ex b/apps/guildhall_web/lib/guildhall_web_web/live/auth_hooks.ex
new file mode 100644
index 0000000..922d6da
--- /dev/null
+++ b/apps/guildhall_web/lib/guildhall_web_web/live/auth_hooks.ex
@@ -0,0 +1,16 @@
+defmodule GuildhallWeb.AuthHooks do
+ @moduledoc false
+ import Phoenix.LiveView
+ import Phoenix.Component
+
+ def on_mount(:require_auth, _params, session, socket) do
+ case session["current_user"] do
+ nil -> {:halt, redirect(socket, to: "/auth/login")}
+ user -> {:cont, assign(socket, :current_user, user)}
+ end
+ end
+
+ def on_mount(:fetch_user, _params, session, socket) do
+ {:cont, assign(socket, :current_user, session["current_user"])}
+ end
+end
diff --git a/apps/guildhall_web/lib/guildhall_web_web/live/dashboard_live.ex b/apps/guildhall_web/lib/guildhall_web_web/live/dashboard_live.ex
index d8fbf1e..cb54b37 100644
--- a/apps/guildhall_web/lib/guildhall_web_web/live/dashboard_live.ex
+++ b/apps/guildhall_web/lib/guildhall_web_web/live/dashboard_live.ex
@@ -5,7 +5,7 @@ defmodule GuildhallWeb.DashboardLive do
"""
use GuildhallWeb, :live_view
- alias Guildhall.OpsDb.{Repo, GovernedArtifact, DeploymentState, VerificationResult}
+ alias Guildhall.OpsDb.{Repo, GovernedArtifact, DeploymentState, VerificationResult, Guilds}
import Ecto.Query
@impl true
@@ -35,6 +35,9 @@ defmodule GuildhallWeb.DashboardLive do
|> assign(:healthy_count, count_by_drift("match"))
|> assign(:drifted_count, count_by_drift("drift"))
|> assign(:recent_verifications, recent_verifications(5))
+ |> assign(:guild_total, Guilds.guild_count())
+ |> assign(:guild_pending, Guilds.guild_count("pending_approval"))
+ |> assign(:guild_active, Guilds.guild_count("active"))
end
defp count_by_drift(status) do
@@ -77,7 +80,23 @@ defmodule GuildhallWeb.DashboardLive do
+
{length(@guilds)} registered.
+| Name | +Type | +Guild ID | +Status | +Registrant | +Actions | +
|---|---|---|---|---|---|
| + <.link navigate={~p"/guilds/#{g.slug}"} class="text-blue-600 underline">{g.name} + | +{type_label(g.guild_type)} | +0x{Integer.to_string(g.guild_id, 16) |> String.pad_leading(3, "0")} | +{g.status} | +{g.registrant_did} | +
+
+
+
+
+ |
+
| No guilds registered yet. | +|||||
{@guild.description || "No description."}
+Type: {String.upcase(@guild.guild_type)}
+{length(@memberships)} member(s).
+| Name | +Role | +Status | +Actions | +|
|---|---|---|---|---|
| {m.display_name || "—"} | +{m.user_email} | +{m.role} | +{m.status} | +
+
+
+
+
+
+
+
+ |
+
| No members yet. | +||||
Submit a new guild for hub operator approval.
++ {error_to_string(@form[:name].errors)} +
++ Template: {@guild.guild_type}-founding +
+{inspect(@template, pretty: true, limit: :infinity)}
+ This will:
+{@guild.description}
++ Ceremony: {@guild.registration_ceremony_id} +
++ Status: {@ceremony_status} +
+ +This guild registration was denied.
+