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}.")