Update the persistence schema specification
This commit is contained in:
@@ -92,6 +92,78 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mcp_proposal_enum_constraints_round_trip_and_reject_unknown_values() {
|
||||||
|
use crate::db::queries::{mcp_proposal, project};
|
||||||
|
use crate::model::{McpProposal, ProposalKind, ProposalStatus};
|
||||||
|
|
||||||
|
let db = migrated_database();
|
||||||
|
project::insert_project(db.conn(), &project::make_test_project("p1", "blog")).unwrap();
|
||||||
|
let kinds = [
|
||||||
|
ProposalKind::DraftPost,
|
||||||
|
ProposalKind::ProposeScript,
|
||||||
|
ProposalKind::ProposeTemplate,
|
||||||
|
ProposalKind::ProposeMediaTranslation,
|
||||||
|
ProposalKind::ProposeMediaMetadata,
|
||||||
|
ProposalKind::ProposePostMetadata,
|
||||||
|
];
|
||||||
|
let statuses = [
|
||||||
|
ProposalStatus::Pending,
|
||||||
|
ProposalStatus::Executing,
|
||||||
|
ProposalStatus::Accepted,
|
||||||
|
ProposalStatus::Rejected,
|
||||||
|
ProposalStatus::Expired,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (index, kind) in kinds.into_iter().enumerate() {
|
||||||
|
let status = statuses[index % statuses.len()];
|
||||||
|
let id = format!("proposal-{index}");
|
||||||
|
let proposal = McpProposal {
|
||||||
|
id: id.clone(),
|
||||||
|
project_id: "p1".into(),
|
||||||
|
kind,
|
||||||
|
status,
|
||||||
|
entity_id: id.clone(),
|
||||||
|
data: "{}".into(),
|
||||||
|
result: None,
|
||||||
|
created_at: 1,
|
||||||
|
expires_at: 2,
|
||||||
|
resolved_at: None,
|
||||||
|
};
|
||||||
|
mcp_proposal::insert_proposal(db.conn(), &proposal).unwrap();
|
||||||
|
let stored = mcp_proposal::get_proposal(db.conn(), &id).unwrap();
|
||||||
|
assert_eq!(stored.kind, kind);
|
||||||
|
assert_eq!(stored.status, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id, kind, status) in [
|
||||||
|
("invalid-kind", "unknown", "pending"),
|
||||||
|
("invalid-status", "draft_post", "unknown"),
|
||||||
|
] {
|
||||||
|
let result = db.conn().with(|connection| {
|
||||||
|
diesel::insert_into(mcp_proposals::table)
|
||||||
|
.values((
|
||||||
|
mcp_proposals::id.eq(id),
|
||||||
|
mcp_proposals::project_id.eq("p1"),
|
||||||
|
mcp_proposals::kind.eq(kind),
|
||||||
|
mcp_proposals::status.eq(status),
|
||||||
|
mcp_proposals::entity_id.eq(id),
|
||||||
|
mcp_proposals::data.eq("{}"),
|
||||||
|
mcp_proposals::created_at.eq(1_i64),
|
||||||
|
mcp_proposals::expires_at.eq(2_i64),
|
||||||
|
))
|
||||||
|
.execute(connection)
|
||||||
|
});
|
||||||
|
assert!(matches!(
|
||||||
|
result,
|
||||||
|
Err(diesel::result::Error::DatabaseError(
|
||||||
|
diesel::result::DatabaseErrorKind::CheckViolation,
|
||||||
|
_
|
||||||
|
))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn migrations_create_every_persisted_table() {
|
fn migrations_create_every_persisted_table() {
|
||||||
let db = migrated_database();
|
let db = migrated_database();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
-- allium: 1
|
-- allium: 1
|
||||||
-- bDS Persistence Data Contract
|
-- bDS Persistence Data Contract
|
||||||
-- Scope: core (Wave 1 — exact compatibility contract)
|
-- Scope: core (Wave 1 — exact compatibility contract)
|
||||||
-- Distilled from: ../bDS2/src/main/database/schema.ts
|
-- Distilled from: crates/bds-core/migrations/ and ../bDS2/priv/repo/migrations/
|
||||||
--
|
--
|
||||||
-- This document specifies the persisted data model the rewrite must be able
|
-- This document specifies the persisted data model the rewrite must be able
|
||||||
-- to read and write. It is the ground truth for storage compatibility.
|
-- to read and write. It is the ground truth for storage compatibility.
|
||||||
@@ -27,6 +27,23 @@ enum ScriptStatus {
|
|||||||
published
|
published
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum McpProposalKind {
|
||||||
|
draft_post
|
||||||
|
propose_script
|
||||||
|
propose_template
|
||||||
|
propose_media_translation
|
||||||
|
propose_media_metadata
|
||||||
|
propose_post_metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
enum McpProposalStatus {
|
||||||
|
pending
|
||||||
|
executing
|
||||||
|
accepted
|
||||||
|
rejected
|
||||||
|
expired
|
||||||
|
}
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
-- CORE ENTITIES
|
-- CORE ENTITIES
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
@@ -229,6 +246,7 @@ entity ChatConversation {
|
|||||||
title: String
|
title: String
|
||||||
model: String? -- Model used for conversation
|
model: String? -- Model used for conversation
|
||||||
copilot_session_id: String? -- Legacy, no longer used
|
copilot_session_id: String? -- Legacy, no longer used
|
||||||
|
surface_state: String? -- Serialized form/tab/dismissal state for inline chat surfaces
|
||||||
created_at: Timestamp
|
created_at: Timestamp
|
||||||
updated_at: Timestamp
|
updated_at: Timestamp
|
||||||
}
|
}
|
||||||
@@ -315,6 +333,26 @@ entity AiEndpointModel {
|
|||||||
updated_at: Timestamp
|
updated_at: Timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entity McpProposal {
|
||||||
|
-- RuDS persistence record. bDS2 stores the common kind, status,
|
||||||
|
-- entity_id, data, created_at, and expires_at subset without project,
|
||||||
|
-- result, or resolution timestamps.
|
||||||
|
id: String -- UUID v4
|
||||||
|
project_id: String
|
||||||
|
kind: McpProposalKind
|
||||||
|
status: McpProposalStatus
|
||||||
|
entity_id: String
|
||||||
|
data: String -- Serialized inert proposal payload
|
||||||
|
result: String? -- Serialized resolution result
|
||||||
|
created_at: Timestamp
|
||||||
|
expires_at: Timestamp
|
||||||
|
resolved_at: Timestamp?
|
||||||
|
}
|
||||||
|
|
||||||
|
-- INTENTIONAL DIVERGENCE: bDS2 persists publish_jobs, but RuDS keeps publish
|
||||||
|
-- jobs in memory. Maintainer decision #86 found no value in persisted job
|
||||||
|
-- history, so no PublishJob persistence entity or table exists in RuDS.
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
-- EMBEDDINGS TABLES
|
-- EMBEDDINGS TABLES
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
@@ -528,6 +566,7 @@ surface ChatConversationRecordSurface {
|
|||||||
conversation.title
|
conversation.title
|
||||||
conversation.model when conversation.model != null
|
conversation.model when conversation.model != null
|
||||||
conversation.copilot_session_id when conversation.copilot_session_id != null
|
conversation.copilot_session_id when conversation.copilot_session_id != null
|
||||||
|
conversation.surface_state when conversation.surface_state != null
|
||||||
conversation.created_at
|
conversation.created_at
|
||||||
conversation.updated_at
|
conversation.updated_at
|
||||||
}
|
}
|
||||||
@@ -610,6 +649,22 @@ surface AiEndpointModelRecordSurface {
|
|||||||
endpoint_model.updated_at
|
endpoint_model.updated_at
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface McpProposalRecordSurface {
|
||||||
|
context proposal: McpProposal
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
proposal.id
|
||||||
|
proposal.project_id
|
||||||
|
proposal.kind
|
||||||
|
proposal.status
|
||||||
|
proposal.entity_id
|
||||||
|
proposal.data
|
||||||
|
proposal.result when proposal.result != null
|
||||||
|
proposal.created_at
|
||||||
|
proposal.expires_at
|
||||||
|
proposal.resolved_at when proposal.resolved_at != null
|
||||||
|
}
|
||||||
|
|
||||||
surface EmbeddingKeyRecordSurface {
|
surface EmbeddingKeyRecordSurface {
|
||||||
context key: EmbeddingKey
|
context key: EmbeddingKey
|
||||||
|
|
||||||
@@ -759,6 +814,16 @@ invariant UniqueDismissedDuplicatePair {
|
|||||||
and a.post_id_b = b.post_id_b)
|
and a.post_id_b = b.post_id_b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
invariant UniqueMcpProposalEntityStatus {
|
||||||
|
-- Enforced by SQLite unique index mcp_proposals_entity_idx.
|
||||||
|
for a in McpProposals:
|
||||||
|
for b in McpProposals:
|
||||||
|
a != b implies
|
||||||
|
not (a.kind = b.kind
|
||||||
|
and a.entity_id = b.entity_id
|
||||||
|
and a.status = b.status)
|
||||||
|
}
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
-- FTS5 VIRTUAL TABLE SCHEMAS (Snowball Stemmer Integration)
|
-- FTS5 VIRTUAL TABLE SCHEMAS (Snowball Stemmer Integration)
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
@@ -791,7 +856,8 @@ value Fts5MediaSchema {
|
|||||||
|
|
||||||
value MigrationVersion {
|
value MigrationVersion {
|
||||||
-- Schema version tracking via embedded Diesel migrations
|
-- Schema version tracking via embedded Diesel migrations
|
||||||
-- Current version: 20260718000000 (initial compatible schema baseline)
|
-- Current version: 2026-07-22-161429-0000
|
||||||
|
-- (MCP proposal entity/status uniqueness and non-null entity IDs)
|
||||||
-- Migration directories located in: crates/bds-core/migrations/
|
-- Migration directories located in: crates/bds-core/migrations/
|
||||||
-- Note: Migration list documented in comments, not as Allium value
|
-- Note: Migration list documented in comments, not as Allium value
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user