Files
ss-tools/specs/042-dashboard-scenario-registry/data-model.md

8.6 KiB

#region ScenarioRegistry.DataModel [C:4] [TYPE ADR] [SEMANTICS data-model,scenario,registry,revision,lifecycle] @BRIEF Canonical registry entry, revision chain, lifecycle state machine, and staleness signal models. @RELATION DEPENDS_ON -> [ScenarioRegistry.Research] @RATIONALE A queryable registry projection decouples scenario management from git-file materialization; typed lifecycle/staleness make states auditable and reproducible. @REJECTED Free-form status strings — rejected because only valid transitions must apply and be audit-logged.

ScenarioRegistryEntry — entity identity (revision for #7/#8)

Fields: scenario_id (UUID — entity identity), scenario_key (semantic slug = dashboard + normalized objective, domain/human identity), name, description, dashboard_id, environment_ids (compat), owner_id, owner_username, tags (list), metadata_version (opaque ETag for metadata-only optimistic concurrency), current_revision (revision_id), lifecycle_status (enum), validation_status (enum), last_run (ref), last_successful_run (ref), health (derived: pass/warn/fail), last_modified_at, baseline_compatibility (version), created_at.

Identity rule: scenario_id MUST be a UUID to survive clone (clones of the same dashboard+objective must NOT collide). scenario_key is the human/domain identity (may repeat across clones).

Indexes: dashboard_id, owner_id, lifecycle_status, tags (GIN), name (trigram for search), scenario_key.

ScenarioRevision — revision identity (revision for #7)

Fields: revision_id (UUID — unique immutable identity), scenario_id, content_hash (SHA-256 of the executable canonical Verification Program graph, including SQL templates/hashes, DSL, assertions and AgentEvaluationSpec; timestamps/display-only excluded), parent_revision_id (nullable), graph_snapshot (DashboardTestScenario JSON), execution_template_hash (hash of revision-derived execution template only; no environment/ParameterBinding/baselines), template_version, schema_version, compatibility_family, change_summary (added/changed/removed), created_by, created_at, activation_status (candidate|current), activated_by?, activated_at?, activation_agent_action_id?. is_current is a read-model convenience derived only from activation_status=current.

Metadata vs executable split (#8): entity-metadata edits (name/description/tags) update ScenarioRegistryEntry fields and DO NOT create a new executable ScenarioRevision (content_hash unchanged). Only executable-graph edits create a new revision. revision_id (UUID) is the unique identity; content_hash detects content change.

Clone provenance: cloning creates a new scenario_id and a new initial revision with parent_revision_id=null; ForkProvenance { source_scenario_id, source_revision_id } preserves its origin. Cross-scenario revisions MUST NOT be joined by parent_revision_id.

CreateScenario — server-owned handle + outbox saga (#1/#5/#6)

CreateScenario never accepts an arbitrary client graph, draft pack, or owner. The authoring path creates server-owned immutable handles:

  • CompiledScenarioHandle { handle_id, content_hash, dashboard_id, validation_status=valid } from 038;
  • DraftPackHandle { draft_pack_id, compiled_handle_id, digest, status=save_eligible } from the authoring pack renderer.

The authenticated principal supplies ownership; the client supplies only {compiled_handle_id, draft_pack_id, draft_pack_digest}. In one PostgreSQL transaction, the service verifies handle ownership, matching dashboard/content hash, save_eligible, actor RBAC, and then creates ScenarioRegistryEntry + ScenarioRevision #1 + OutboxEvent(type=materialize_revision). The transaction returns {scenario_id, revision_id, materialization_status=pending}.

Git/filesystem materialization is NOT part of the DB transaction. An idempotent worker consumes the outbox event, writes reference artifacts (scenario.yaml, reference runner.plan.json) keyed by content hash, and updates RevisionMaterialization from pending → materialized | failed. A failed materialization is retryable without duplicating Registry rows; the DB revision remains authoritative.

Agent-created registry mutations use the same server-owned handles and transaction. created_by records the agent identity and delegated analyst; agent_run_id? and investigation_case_id? preserve provenance. A delegated agent may save a validated immutable revision but cannot silently bypass lifecycle transitions, object ACL, materialization, or a required ActionApprovalGate.

Revision save and activation are separate operations

The initial revision created with a new scenario is current. Every later executable save creates an immutable candidate; saving never changes ScenarioRegistryEntry.current_revision. ActivateCurrentRevision is a separate atomic operation: it makes exactly one candidate current, updates the entry pointer, and records actor/agent/policy/gate provenance. A candidate is activation-eligible only if validation and materialization succeeded, it is automation_eligible, contains no human step for automation adoption, passes required verification, does not increase risk outside policy, and is compatible with the current revision (compatibility_family unchanged unless an explicit analyst-approved migration policy permits it).

The server evaluates the versioned 036 DelegatedAuthorityPolicy. An agent may activate only when its recorded policy snapshot explicitly has may_activate_current_revision=true; otherwise the operation creates/consumes an ActionApprovalGate. Schedules with revision_policy=current resolve the pointer only after this atomic activation; pinned schedules may use only an explicitly selected eligible revision.

RevisionMaterialization and OutboxEvent

  • RevisionMaterialization: revision_id, status (pending|materialized|failed), artifact_manifest_hash, attempt_count, last_error, materialized_at.
  • OutboxEvent: event_id, aggregate_type, aggregate_id, event_type, payload, idempotency_key, created_at, delivered_at, attempt_count. Written in the same DB transaction as the registry revision; never lost on worker/broker outage.

LifecycleStateMachine

States: DRAFT, READY, STALE, NEEDS_REVALIDATION, BLOCKED, DISABLED, DEPRECATED, ARCHIVED.

Valid transitions:

  • DRAFT → READY (validation clean) | DEPRECATED
  • READY → STALE | DISABLED | DEPRECATED | ARCHIVED | NEEDS_REVALIDATION
  • STALE → NEEDS_REVALIDATION | READY (revalidated) | DEPRECATED
  • NEEDS_REVALIDATION → READY | BLOCKED | STALE
  • BLOCKED → NEEDS_REVALIDATION | DEPRECATED
  • DISABLED → READY | ARCHIVED | DEPRECATED
  • DEPRECATED → ARCHIVED
  • ARCHIVED → (terminal; only restore to DRAFT)

Every transition writes an audit record: scenario_id, from, to, actor_id, reason, timestamp. Full command surface is generic transition (disable, enable→READY, deprecate, archive, restore) rather than archive/restore-only endpoints.

ScenarioStalenessSignal

Fields: id, scenario_id, source_type, source_fingerprint, kind (dashboard_release_diff | lineage_blast_radius | reference_removed), severity (info/warning/critical), reason, affected_ref, detected_at, resolved_at (nullable), source (037 StructureDiff | 041 lineage). Unique identity is (scenario_id, source_type, source_fingerprint, affected_ref); repeated events upsert rather than duplicate a signal. Lifecycle derives from the aggregate of active signals: READY is permitted only when no active critical/blocking signal remains.

Health projection

042 does not derive health. It consumes only 047 ScenarioHealth.overall_attention as the registry badge; if analytics is unavailable the badge is unknown. The algorithm and historical windows belong exclusively to 047.

The Registry is also the portfolio entry surface for Investigation Queue. Staleness and health signals may link to queue items; opening one opens its persistent agent case rather than a modal or an automatic chat.

Object-level authorization

  • scenario:view, scenario:create, scenario:edit, scenario:archive, scenario:run (separate from edit). MVP is archive-only: there is no hard-delete route, even for administrators.

Every registry/run/evidence decision additionally requires the intersection of scenario permission, dashboard ACL, environment ACL, artifact/evidence ACL, and the caller's effective Superset/RLS access. A registry permission alone never grants screenshot, XLSX, VLM, or result access.

Storage Notes

  • Registry projection is authoritative for list/detail/status; git scenario.yaml/reference runner.plan.json are asynchronously materialized artifacts with explicit status.
  • Revisions are immutable and append-only; a new edit creates a new row and advances current_revision.

#endregion ScenarioRegistry.DataModel