From 4d9acf96d822e50ab9367631a51086d9bdd307c847eb6796a39701385771c57e Mon Sep 17 00:00:00 2001 From: Tyler J King Date: Sat, 18 Apr 2026 07:17:51 -0400 Subject: [PATCH] feat(ops_db): Ecto schemas for five Ops DB tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Direct translation of DESIGN-OPS-DB-CHAIN-OF-CUSTODY-0001 §2.2: - AccordBinding: governance context per artifact - GovernedArtifact: registry with JSONB content + content_schema - CustodyTransition: append-only chain of custody - DeploymentState: current deployment per (artifact, target) - VerificationResult: continuous attestation records - ProjectorCheckpoint: Chronicle projector resume state All JSONB fields use Ecto :map type (Postgres JSONB). GIN indexes (jsonb_path_ops) on accord_terms and content columns. Partitioning notes for time-series tables (DBA applies in prod). Migrations renumbered to enforce FK dependency order (accord_bindings → governed_artifacts → dependents). Dev seed data (priv/repo/seeds.exs) creates nine governed artifacts matching the JSONB content examples in the design doc §2.3 (Intune profile, DNS zone, DSC MOF, YANG config, Helm values, JEA role, Conditional Access, TLS cert, OCI image) plus custody transitions, deployment states, and verification results. Verified: mix ecto.create + mix ecto.migrate + mix run seeds all pass; 9 artifacts in governed_artifacts table. Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: Tyler J King --- .../lib/guildhall/ops_db/accord_binding.ex | 47 ++++ .../guildhall/ops_db/custody_transition.ex | 46 ++++ .../lib/guildhall/ops_db/deployment_state.ex | 47 ++++ .../lib/guildhall/ops_db/governed_artifact.ex | 53 ++++ .../guildhall/ops_db/projector_checkpoint.ex | 22 ++ .../guildhall/ops_db/verification_result.ex | 46 ++++ .../20260418110937_create_accord_bindings.exs | 29 +++ ...260418110938_create_governed_artifacts.exs | 32 +++ ...60418110940_create_custody_transitions.exs | 33 +++ ...0260418110941_create_deployment_states.exs | 31 +++ ...0418110942_create_verification_results.exs | 30 +++ ...0418110943_create_projector_checkpoint.exs | 17 ++ apps/guildhall_ops_db/priv/repo/seeds.exs | 238 ++++++++++++++++++ 13 files changed, 671 insertions(+) create mode 100644 apps/guildhall_ops_db/lib/guildhall/ops_db/accord_binding.ex create mode 100644 apps/guildhall_ops_db/lib/guildhall/ops_db/custody_transition.ex create mode 100644 apps/guildhall_ops_db/lib/guildhall/ops_db/deployment_state.ex create mode 100644 apps/guildhall_ops_db/lib/guildhall/ops_db/governed_artifact.ex create mode 100644 apps/guildhall_ops_db/lib/guildhall/ops_db/projector_checkpoint.ex create mode 100644 apps/guildhall_ops_db/lib/guildhall/ops_db/verification_result.ex create mode 100644 apps/guildhall_ops_db/priv/repo/migrations/20260418110937_create_accord_bindings.exs create mode 100644 apps/guildhall_ops_db/priv/repo/migrations/20260418110938_create_governed_artifacts.exs create mode 100644 apps/guildhall_ops_db/priv/repo/migrations/20260418110940_create_custody_transitions.exs create mode 100644 apps/guildhall_ops_db/priv/repo/migrations/20260418110941_create_deployment_states.exs create mode 100644 apps/guildhall_ops_db/priv/repo/migrations/20260418110942_create_verification_results.exs create mode 100644 apps/guildhall_ops_db/priv/repo/migrations/20260418110943_create_projector_checkpoint.exs create mode 100644 apps/guildhall_ops_db/priv/repo/seeds.exs diff --git a/apps/guildhall_ops_db/lib/guildhall/ops_db/accord_binding.ex b/apps/guildhall_ops_db/lib/guildhall/ops_db/accord_binding.ex new file mode 100644 index 0000000..dbe56f9 --- /dev/null +++ b/apps/guildhall_ops_db/lib/guildhall/ops_db/accord_binding.ex @@ -0,0 +1,47 @@ +defmodule Guildhall.OpsDb.AccordBinding do + @moduledoc """ + Governance context under which an artifact is governed. + Schema per DESIGN-OPS-DB-CHAIN-OF-CUSTODY-0001 §2.2.5. + """ + use Ecto.Schema + import Ecto.Changeset + + @type t :: %__MODULE__{} + + schema "accord_bindings" do + field :accord_cid, :string + field :accord_name, :string + field :accord_terms, :map, default: %{} + field :member_dids, {:array, :string}, default: [] + field :witness_dids, {:array, :string}, default: [] + field :tier_policy, :map, default: %{} + field :ceremony_classes, {:array, :string}, default: [] + field :verification_interval_sec, :integer + field :chronicle_retention_days, :integer + field :effective_from, :utc_datetime_usec + field :effective_until, :utc_datetime_usec + + has_many :governed_artifacts, Guildhall.OpsDb.GovernedArtifact + + timestamps(type: :utc_datetime_usec, inserted_at: :created_at, updated_at: false) + end + + def changeset(binding, attrs) do + binding + |> cast(attrs, [ + :accord_cid, + :accord_name, + :accord_terms, + :member_dids, + :witness_dids, + :tier_policy, + :ceremony_classes, + :verification_interval_sec, + :chronicle_retention_days, + :effective_from, + :effective_until + ]) + |> validate_required([:accord_cid, :accord_name]) + |> unique_constraint(:accord_cid) + end +end diff --git a/apps/guildhall_ops_db/lib/guildhall/ops_db/custody_transition.ex b/apps/guildhall_ops_db/lib/guildhall/ops_db/custody_transition.ex new file mode 100644 index 0000000..642cb6c --- /dev/null +++ b/apps/guildhall_ops_db/lib/guildhall/ops_db/custody_transition.ex @@ -0,0 +1,46 @@ +defmodule Guildhall.OpsDb.CustodyTransition do + @moduledoc """ + Append-only chain of custody. Each row is one custody event + ordered by `sequence_number`. + Schema per DESIGN-OPS-DB-CHAIN-OF-CUSTODY-0001 §2.2.2. + """ + use Ecto.Schema + import Ecto.Changeset + + @type t :: %__MODULE__{} + + @primary_key {:id, :id, autogenerate: true} + schema "custody_transitions" do + field :sequence_number, :integer + field :from_state, :string + field :to_state, :string + field :actor_did, :string + field :ceremony_id, :string + field :witness_sigs, {:array, :map}, default: [] + field :governance_envelope_hash, :string + field :chronicle_entry_id, :string + field :content_diff, :map, default: %{} + field :transitioned_at, :utc_datetime_usec + + belongs_to :governed_artifact, Guildhall.OpsDb.GovernedArtifact + end + + def changeset(transition, attrs) do + transition + |> cast(attrs, [ + :sequence_number, + :from_state, + :to_state, + :actor_did, + :ceremony_id, + :witness_sigs, + :governance_envelope_hash, + :chronicle_entry_id, + :content_diff, + :transitioned_at, + :governed_artifact_id + ]) + |> validate_required([:sequence_number, :to_state, :transitioned_at, :governed_artifact_id]) + |> foreign_key_constraint(:governed_artifact_id) + end +end diff --git a/apps/guildhall_ops_db/lib/guildhall/ops_db/deployment_state.ex b/apps/guildhall_ops_db/lib/guildhall/ops_db/deployment_state.ex new file mode 100644 index 0000000..2b5d522 --- /dev/null +++ b/apps/guildhall_ops_db/lib/guildhall/ops_db/deployment_state.ex @@ -0,0 +1,47 @@ +defmodule Guildhall.OpsDb.DeploymentState do + @moduledoc """ + Current deployment per (artifact, target). Updated on each deployment, + rewound on rollback. + Schema per DESIGN-OPS-DB-CHAIN-OF-CUSTODY-0001 §2.2.3. + """ + use Ecto.Schema + import Ecto.Changeset + + @type t :: %__MODULE__{} + + schema "deployment_states" do + field :target_kind, :string + field :target_identifier, :string + field :deployed_cid, :string + field :desired_cid, :string + field :target_config, :map, default: %{} + field :last_deployed_at, :utc_datetime_usec + field :last_reconciled_at, :utc_datetime_usec + field :drift_status, :string + + belongs_to :governed_artifact, Guildhall.OpsDb.GovernedArtifact + + timestamps(type: :utc_datetime_usec) + end + + def changeset(state, attrs) do + state + |> cast(attrs, [ + :target_kind, + :target_identifier, + :deployed_cid, + :desired_cid, + :target_config, + :last_deployed_at, + :last_reconciled_at, + :drift_status, + :governed_artifact_id + ]) + |> validate_required([:target_kind, :target_identifier, :governed_artifact_id]) + |> validate_inclusion(:drift_status, [nil, "match", "drift", "unknown"]) + |> unique_constraint([:governed_artifact_id, :target_kind, :target_identifier], + name: :deployment_states_artifact_target_uniq + ) + |> foreign_key_constraint(:governed_artifact_id) + end +end diff --git a/apps/guildhall_ops_db/lib/guildhall/ops_db/governed_artifact.ex b/apps/guildhall_ops_db/lib/guildhall/ops_db/governed_artifact.ex new file mode 100644 index 0000000..634ad73 --- /dev/null +++ b/apps/guildhall_ops_db/lib/guildhall/ops_db/governed_artifact.ex @@ -0,0 +1,53 @@ +defmodule Guildhall.OpsDb.GovernedArtifact do + @moduledoc """ + Registry of governed artifacts. + Schema per DESIGN-OPS-DB-CHAIN-OF-CUSTODY-0001 §2.2.1. + """ + use Ecto.Schema + import Ecto.Changeset + + @type t :: %__MODULE__{} + + schema "governed_artifacts" do + field :cid, :string + field :artifact_type, :string + field :artifact_name, :string + field :artifact_runtime, :string + field :content, :map, default: %{} + field :content_schema, :map, default: %{} + field :sbom_cid, :string + field :tier, :string + field :shell_class_required, :string + field :chronicle_entry_id, :string + field :human_authored, :boolean, default: true + + belongs_to :accord_binding, Guildhall.OpsDb.AccordBinding + + has_many :custody_transitions, Guildhall.OpsDb.CustodyTransition + has_many :deployment_states, Guildhall.OpsDb.DeploymentState + has_many :verification_results, Guildhall.OpsDb.VerificationResult + + timestamps(type: :utc_datetime_usec, inserted_at: :created_at, updated_at: false) + end + + def changeset(artifact, attrs) do + artifact + |> cast(attrs, [ + :cid, + :artifact_type, + :artifact_name, + :artifact_runtime, + :content, + :content_schema, + :sbom_cid, + :tier, + :shell_class_required, + :chronicle_entry_id, + :human_authored, + :accord_binding_id + ]) + |> validate_required([:cid, :artifact_type, :artifact_name]) + |> unique_constraint(:cid) + |> foreign_key_constraint(:accord_binding_id) + end +end diff --git a/apps/guildhall_ops_db/lib/guildhall/ops_db/projector_checkpoint.ex b/apps/guildhall_ops_db/lib/guildhall/ops_db/projector_checkpoint.ex new file mode 100644 index 0000000..cd436a7 --- /dev/null +++ b/apps/guildhall_ops_db/lib/guildhall/ops_db/projector_checkpoint.ex @@ -0,0 +1,22 @@ +defmodule Guildhall.OpsDb.ProjectorCheckpoint do + @moduledoc """ + Chronicle projector resume state. See + DESIGN-OPS-DB-CHAIN-OF-CUSTODY-0001 §2.5. + """ + use Ecto.Schema + import Ecto.Changeset + + @type t :: %__MODULE__{} + + @primary_key {:source, :string, autogenerate: false} + schema "projector_checkpoint" do + field :last_entry_id, :string + field :last_processed_at, :utc_datetime_usec + end + + def changeset(checkpoint, attrs) do + checkpoint + |> cast(attrs, [:source, :last_entry_id, :last_processed_at]) + |> validate_required([:source]) + end +end diff --git a/apps/guildhall_ops_db/lib/guildhall/ops_db/verification_result.ex b/apps/guildhall_ops_db/lib/guildhall/ops_db/verification_result.ex new file mode 100644 index 0000000..bd82cb3 --- /dev/null +++ b/apps/guildhall_ops_db/lib/guildhall/ops_db/verification_result.ex @@ -0,0 +1,46 @@ +defmodule Guildhall.OpsDb.VerificationResult do + @moduledoc """ + Continuous attestation records. High-volume time-series table; + partitioned monthly in production. + Schema per DESIGN-OPS-DB-CHAIN-OF-CUSTODY-0001 §2.2.4. + """ + use Ecto.Schema + import Ecto.Changeset + + @type t :: %__MODULE__{} + + schema "verification_results" do + field :verified_cid, :string + field :target_kind, :string + field :target_identifier, :string + field :layer, :string + field :verdict, :string + field :evidence, :map, default: %{} + field :verified_at, :utc_datetime_usec + field :verifier_did, :string + field :verifier_infrastructure, :string + + belongs_to :governed_artifact, Guildhall.OpsDb.GovernedArtifact + end + + def changeset(result, attrs) do + result + |> cast(attrs, [ + :verified_cid, + :target_kind, + :target_identifier, + :layer, + :verdict, + :evidence, + :verified_at, + :verifier_did, + :verifier_infrastructure, + :governed_artifact_id + ]) + |> validate_required([:verified_cid, :verdict, :verified_at, :governed_artifact_id]) + |> validate_inclusion(:verdict, ["match", "drift", "error"]) + |> validate_inclusion(:layer, [nil, "layer1", "layer2", "layer3"]) + |> validate_inclusion(:verifier_infrastructure, [nil, "local", "external", "insurer"]) + |> foreign_key_constraint(:governed_artifact_id) + end +end diff --git a/apps/guildhall_ops_db/priv/repo/migrations/20260418110937_create_accord_bindings.exs b/apps/guildhall_ops_db/priv/repo/migrations/20260418110937_create_accord_bindings.exs new file mode 100644 index 0000000..acd9dc4 --- /dev/null +++ b/apps/guildhall_ops_db/priv/repo/migrations/20260418110937_create_accord_bindings.exs @@ -0,0 +1,29 @@ +defmodule Guildhall.OpsDb.Repo.Migrations.CreateAccordBindings do + use Ecto.Migration + + def change do + create table(:accord_bindings) do + add :accord_cid, :string, null: false + add :accord_name, :string, null: false + add :accord_terms, :map, null: false, default: %{} + add :member_dids, {:array, :string}, default: [] + add :witness_dids, {:array, :string}, default: [] + add :tier_policy, :map, default: %{} + add :ceremony_classes, {:array, :string}, default: [] + add :verification_interval_sec, :integer + add :chronicle_retention_days, :integer + add :effective_from, :utc_datetime_usec + add :effective_until, :utc_datetime_usec + + timestamps(type: :utc_datetime_usec, inserted_at: :created_at, updated_at: false) + end + + create unique_index(:accord_bindings, [:accord_cid]) + create index(:accord_bindings, [:accord_name]) + + execute( + "CREATE INDEX accord_bindings_terms_gin_idx ON accord_bindings USING GIN (accord_terms jsonb_path_ops)", + "DROP INDEX accord_bindings_terms_gin_idx" + ) + end +end diff --git a/apps/guildhall_ops_db/priv/repo/migrations/20260418110938_create_governed_artifacts.exs b/apps/guildhall_ops_db/priv/repo/migrations/20260418110938_create_governed_artifacts.exs new file mode 100644 index 0000000..28686b4 --- /dev/null +++ b/apps/guildhall_ops_db/priv/repo/migrations/20260418110938_create_governed_artifacts.exs @@ -0,0 +1,32 @@ +defmodule Guildhall.OpsDb.Repo.Migrations.CreateGovernedArtifacts do + use Ecto.Migration + + def change do + create table(:governed_artifacts) do + add :cid, :string, null: false + add :artifact_type, :string, null: false + add :artifact_name, :string, null: false + add :artifact_runtime, :string + add :content, :map, null: false, default: %{} + add :content_schema, :map, default: %{} + add :sbom_cid, :string + add :tier, :string + add :shell_class_required, :string + add :chronicle_entry_id, :string + add :human_authored, :boolean, default: true + add :accord_binding_id, references(:accord_bindings, on_delete: :restrict) + + timestamps(type: :utc_datetime_usec, inserted_at: :created_at, updated_at: false) + end + + create unique_index(:governed_artifacts, [:cid]) + create index(:governed_artifacts, [:artifact_type]) + create index(:governed_artifacts, [:accord_binding_id]) + create index(:governed_artifacts, [:tier]) + + execute( + "CREATE INDEX governed_artifacts_content_gin_idx ON governed_artifacts USING GIN (content jsonb_path_ops)", + "DROP INDEX governed_artifacts_content_gin_idx" + ) + end +end diff --git a/apps/guildhall_ops_db/priv/repo/migrations/20260418110940_create_custody_transitions.exs b/apps/guildhall_ops_db/priv/repo/migrations/20260418110940_create_custody_transitions.exs new file mode 100644 index 0000000..5a92d6a --- /dev/null +++ b/apps/guildhall_ops_db/priv/repo/migrations/20260418110940_create_custody_transitions.exs @@ -0,0 +1,33 @@ +defmodule Guildhall.OpsDb.Repo.Migrations.CreateCustodyTransitions do + @moduledoc """ + Append-only chain of custody. Partitioning note: production deployments + should apply RANGE partitioning on `transitioned_at` (quarterly) per + DESIGN-OPS-DB-CHAIN-OF-CUSTODY-0001 §2.2.2. The DBA applies partition + DDL after migration; Ecto does not manage partitions. + """ + use Ecto.Migration + + def change do + create table(:custody_transitions) do + add :sequence_number, :bigint, null: false + add :from_state, :string + add :to_state, :string, null: false + add :actor_did, :string + add :ceremony_id, :string + add :witness_sigs, {:array, :map}, default: [] + add :governance_envelope_hash, :string + add :chronicle_entry_id, :string + add :content_diff, :map, default: %{} + add :transitioned_at, :utc_datetime_usec, null: false + + add :governed_artifact_id, + references(:governed_artifacts, on_delete: :restrict), + null: false + end + + create index(:custody_transitions, [:governed_artifact_id, :sequence_number]) + create index(:custody_transitions, [:ceremony_id]) + create index(:custody_transitions, [:actor_did]) + create index(:custody_transitions, [:transitioned_at]) + end +end diff --git a/apps/guildhall_ops_db/priv/repo/migrations/20260418110941_create_deployment_states.exs b/apps/guildhall_ops_db/priv/repo/migrations/20260418110941_create_deployment_states.exs new file mode 100644 index 0000000..89a12d1 --- /dev/null +++ b/apps/guildhall_ops_db/priv/repo/migrations/20260418110941_create_deployment_states.exs @@ -0,0 +1,31 @@ +defmodule Guildhall.OpsDb.Repo.Migrations.CreateDeploymentStates do + use Ecto.Migration + + def change do + create table(:deployment_states) do + add :target_kind, :string, null: false + add :target_identifier, :string, null: false + add :deployed_cid, :string + add :desired_cid, :string + add :target_config, :map, default: %{} + add :last_deployed_at, :utc_datetime_usec + add :last_reconciled_at, :utc_datetime_usec + add :drift_status, :string + + add :governed_artifact_id, + references(:governed_artifacts, on_delete: :restrict), + null: false + + timestamps(type: :utc_datetime_usec) + end + + create unique_index( + :deployment_states, + [:governed_artifact_id, :target_kind, :target_identifier], + name: :deployment_states_artifact_target_uniq + ) + + create index(:deployment_states, [:drift_status]) + create index(:deployment_states, [:target_kind]) + end +end diff --git a/apps/guildhall_ops_db/priv/repo/migrations/20260418110942_create_verification_results.exs b/apps/guildhall_ops_db/priv/repo/migrations/20260418110942_create_verification_results.exs new file mode 100644 index 0000000..7572d3b --- /dev/null +++ b/apps/guildhall_ops_db/priv/repo/migrations/20260418110942_create_verification_results.exs @@ -0,0 +1,30 @@ +defmodule Guildhall.OpsDb.Repo.Migrations.CreateVerificationResults do + @moduledoc """ + Continuous attestation records. Partitioning note: production deployments + should apply RANGE partitioning on `verified_at` (monthly) per + DESIGN-OPS-DB-CHAIN-OF-CUSTODY-0001 §2.2.4. Ecto does not manage partitions. + """ + use Ecto.Migration + + def change do + create table(:verification_results) do + add :verified_cid, :string, null: false + add :target_kind, :string + add :target_identifier, :string + add :layer, :string + add :verdict, :string, null: false + add :evidence, :map, default: %{} + add :verified_at, :utc_datetime_usec, null: false + add :verifier_did, :string + add :verifier_infrastructure, :string + + add :governed_artifact_id, + references(:governed_artifacts, on_delete: :restrict), + null: false + end + + create index(:verification_results, [:governed_artifact_id, :verified_at]) + create index(:verification_results, [:verdict, :verified_at]) + create index(:verification_results, [:verifier_infrastructure]) + end +end diff --git a/apps/guildhall_ops_db/priv/repo/migrations/20260418110943_create_projector_checkpoint.exs b/apps/guildhall_ops_db/priv/repo/migrations/20260418110943_create_projector_checkpoint.exs new file mode 100644 index 0000000..ba0cc31 --- /dev/null +++ b/apps/guildhall_ops_db/priv/repo/migrations/20260418110943_create_projector_checkpoint.exs @@ -0,0 +1,17 @@ +defmodule Guildhall.OpsDb.Repo.Migrations.CreateProjectorCheckpoint do + @moduledoc """ + Chronicle projector resume state. See §2.5 of + DESIGN-OPS-DB-CHAIN-OF-CUSTODY-0001 — the projector persists its + last processed entry_id so it can resume from the correct position + after a restart. + """ + use Ecto.Migration + + def change do + create table(:projector_checkpoint, primary_key: false) do + add :source, :string, primary_key: true + add :last_entry_id, :string + add :last_processed_at, :utc_datetime_usec + end + end +end diff --git a/apps/guildhall_ops_db/priv/repo/seeds.exs b/apps/guildhall_ops_db/priv/repo/seeds.exs new file mode 100644 index 0000000..8b15b17 --- /dev/null +++ b/apps/guildhall_ops_db/priv/repo/seeds.exs @@ -0,0 +1,238 @@ +# Dev seed data for guildhall_ops_db. +# Matches the nine JSONB content examples in +# DESIGN-OPS-DB-CHAIN-OF-CUSTODY-0001 §2.3. +# +# Run with: mix run apps/guildhall_ops_db/priv/repo/seeds.exs + +alias Guildhall.OpsDb.{ + Repo, + AccordBinding, + GovernedArtifact, + CustodyTransition, + DeploymentState, + VerificationResult +} + +now = DateTime.utc_now() + +# ──────────────────────────────────────────────────────── +# 1. Accord binding (shared by all artifacts below) +# ──────────────────────────────────────────────────────── +accord = + Repo.insert!(%AccordBinding{ + accord_cid: "sha256:dev-accord-example", + accord_name: "acme-saas-dev", + member_dids: ["did:web:guildhouse.dev:user:tking"], + witness_dids: ["did:web:guildhouse.dev:user:tking"], + ceremony_classes: ["SingleApproval"], + verification_interval_sec: 300, + chronicle_retention_days: 90, + effective_from: now, + accord_terms: %{ + "name" => "Development Accord", + "tier_policy" => %{"default" => "a"} + } + }) + +# ──────────────────────────────────────────────────────── +# 2. Governed artifacts — nine examples from §2.3 +# ──────────────────────────────────────────────────────── +artifacts = [ + %{ + cid: "sha256:intune-profile-example", + artifact_type: "intune_config_profile", + artifact_name: "Production Baseline", + artifact_runtime: "intune", + tier: "b", + content: %{ + "@odata.type" => "#microsoft.graph.windows10GeneralConfiguration", + "displayName" => "Production Baseline", + "bitLockerEncryptDevice" => true, + "defenderRequireRealTimeMonitoring" => true, + "passwordMinimumLength" => 12, + "assignments" => [%{"groupId" => "corp-devices"}] + } + }, + %{ + cid: "sha256:dns-zone-example", + artifact_type: "dns_zone", + artifact_name: "guildhouse.dev zone", + artifact_runtime: "zone", + tier: "c", + content: %{ + "zone_apex" => "guildhouse.dev.", + "serial" => 2_026_041_701, + "records" => [ + %{"name" => "@", "type" => "SOA", "rdata" => "ns1.guildhouse.dev. ..."}, + %{"name" => "@", "type" => "NS", "rdata" => "ns1.guildhouse.dev."}, + %{"name" => "auth", "type" => "A", "rdata" => "10.0.0.10"} + ] + } + }, + %{ + cid: "sha256:dsc-bitlocker-example", + artifact_type: "dsc_configuration", + artifact_name: "EnsureBitLockerEnabled", + artifact_runtime: "mof", + tier: "b", + content: %{ + "instances" => [ + %{ + "class" => "MSFT_BitLockerResource", + "name" => "EnsureBitLockerEnabled", + "properties" => %{ + "MountPoint" => "C:", + "PrimaryProtector" => "TpmProtector", + "AutoUnlock" => true + } + } + ] + } + }, + %{ + cid: "sha256:yang-running-config-example", + artifact_type: "restconf_config", + artifact_name: "edge-router-1 interfaces", + artifact_runtime: "json", + tier: "c", + content: %{ + "ietf-interfaces:interfaces" => %{ + "interface" => [ + %{ + "name" => "GigabitEthernet0/0", + "type" => "iana-if-type:ethernetCsmacd", + "enabled" => true + } + ] + } + } + }, + %{ + cid: "sha256:helm-values-example", + artifact_type: "helm_values", + artifact_name: "substrate-operator production values", + artifact_runtime: "values", + tier: "b", + content: %{ + "bound_chart_cid" => "sha256:abc...", + "environment" => "production", + "image" => %{"digest" => "sha256:def..."}, + "replicas" => 3 + } + }, + %{ + cid: "sha256:jea-application-role-example", + artifact_type: "jea_role_capability", + artifact_name: "Application-class JEA role", + artifact_runtime: "psrc", + tier: "c", + content: %{ + "visible_cmdlets" => ["Get-Service", "Get-Process", "Test-DscConfiguration"], + "visible_providers" => ["FileSystem"], + "language_mode" => "ConstrainedLanguage", + "session_type" => "RestrictedRemoteServer" + } + }, + %{ + cid: "sha256:conditional-access-example", + artifact_type: "entra_conditional_access", + artifact_name: "Require compliant device", + artifact_runtime: "json", + tier: "c", + content: %{ + "displayName" => "Require compliant device for corp resources", + "conditions" => %{"users" => %{"includeGroups" => ["all-employees"]}}, + "grantControls" => %{"builtInControls" => ["compliantDevice"]} + } + }, + %{ + cid: "sha256:tls-keycloak-cert-example", + artifact_type: "tls_server_cert", + artifact_name: "keycloak.guildhouse.dev", + artifact_runtime: "x509", + tier: "b", + content: %{ + "subject" => "CN=keycloak.guildhouse.dev", + "not_before" => "2026-04-17T00:00:00Z", + "not_after" => "2027-04-17T00:00:00Z", + "fingerprint_sha256" => "abc...", + "ct_log_refs" => [] + } + }, + %{ + cid: "sha256:oci-image-example", + artifact_type: "oci_image", + artifact_name: "ghcr.io/guildhouse/substrate-operator", + artifact_runtime: "oci", + tier: "b", + content: %{ + "image_reference" => "ghcr.io/guildhouse/substrate-operator", + "digest" => "sha256:a1b2c3...", + "architectures" => ["linux/amd64", "linux/arm64"], + "sbom_cid" => "sha256:..." + } + } +] + +inserted = + Enum.map(artifacts, fn attrs -> + %GovernedArtifact{accord_binding_id: accord.id, human_authored: true} + |> GovernedArtifact.changeset(attrs) + |> Repo.insert!() + end) + +# ──────────────────────────────────────────────────────── +# 3. One custody transition per artifact (proposed → ceremony_open) +# ──────────────────────────────────────────────────────── +Enum.each(inserted, fn artifact -> + Repo.insert!(%CustodyTransition{ + governed_artifact_id: artifact.id, + sequence_number: 1, + from_state: "proposed", + to_state: "ceremony_open", + actor_did: "did:web:guildhouse.dev:user:tking", + ceremony_id: "ceremony-dev-#{artifact.id}", + witness_sigs: [], + transitioned_at: now + }) +end) + +# ──────────────────────────────────────────────────────── +# 4. Mix of deployment_states (healthy / drifted) +# ──────────────────────────────────────────────────────── +inserted +|> Enum.with_index() +|> Enum.each(fn {artifact, idx} -> + drift = if rem(idx, 3) == 0, do: "drift", else: "match" + + Repo.insert!(%DeploymentState{ + governed_artifact_id: artifact.id, + target_kind: "dev-cluster", + target_identifier: "hetzner-talos", + deployed_cid: artifact.cid, + desired_cid: artifact.cid, + last_deployed_at: now, + last_reconciled_at: now, + drift_status: drift + }) +end) + +# ──────────────────────────────────────────────────────── +# 5. Recent verification results +# ──────────────────────────────────────────────────────── +Enum.each(inserted, fn artifact -> + Repo.insert!(%VerificationResult{ + governed_artifact_id: artifact.id, + verified_cid: artifact.cid, + target_kind: "dev-cluster", + target_identifier: "hetzner-talos", + layer: "layer1", + verdict: "match", + verified_at: now, + verifier_did: "did:web:guildhouse.dev:verifier:local", + verifier_infrastructure: "local", + evidence: %{"method" => "seed_data"} + }) +end) + +IO.puts("Seeded #{length(inserted)} governed artifacts under Accord #{accord.accord_name}.")