{"record":{"id":"471756175123749b","repo":"clockworklabs/SpacetimeDB","slug":"table-id-must-not-be-tableid-sentinel-in-ro","errorCode":null,"errorMessage":"`table_id` must not be `TableId::SENTINEL` in `{row_level_security_schema:#?}`","messagePattern":"`table_id` must not be `TableId::SENTINEL` in `(.+?)`","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/datastore/src/locking_tx_datastore/mut_tx.rs","lineNumber":2551,"sourceCode":"        .map(|mut iter| {\n            iter.next()\n                .map(|row| row.read_col(StConstraintFields::ConstraintId).unwrap())\n        })\n    }\n\n    /// Create a row level security policy.\n    ///\n    /// Requires:\n    /// - `row_level_security_schema.table_id != TableId::SENTINEL`\n    /// - `row_level_security_schema.sql` must be unique.\n    ///\n    /// Ensures:\n    ///\n    /// - The row level security policy metadata is inserted into the system tables (and other data structures reflecting them).\n    /// - The returned `sql` is unique.\n    pub fn create_row_level_security(&mut self, row_level_security_schema: RowLevelSecuritySchema) -> Result<RawSql> {\n        if row_level_security_schema.table_id == TableId::SENTINEL {\n            return Err(anyhow::anyhow!(\n                \"`table_id` must not be `TableId::SENTINEL` in `{row_level_security_schema:#?}`\"\n            )\n            .into());\n        }\n\n        log::trace!(\n            \"ROW LEVEL SECURITY CREATING for table: {}\",\n            row_level_security_schema.table_id\n        );\n\n        // Insert the row into st_row_level_security\n        // NOTE: Because st_row_level_security has a unique index on sql, this will\n        // fail if already exists.\n        let row = StRowLevelSecurityRow {\n            table_id: row_level_security_schema.table_id,\n            sql: row_level_security_schema.sql,\n        };\n","sourceCodeStart":2533,"sourceCodeEnd":2569,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/9e0d92412ff2248f401a8ad12d535f2b5ac30912/crates/datastore/src/locking_tx_datastore/mut_tx.rs#L2533-L2569","documentation":"create_row_level_security validates its input: a row-level-security policy must attach to a real table, but the RowLevelSecuritySchema arrived with table_id == TableId::SENTINEL, the placeholder used before the id allocator assigns a real TableId. The call is rejected before any row is written to st_row_level_security.","triggerScenarios":"Building a RowLevelSecuritySchema by hand or via Default/deserialization and calling create_row_level_security before the target table exists or before its assigned id was copied into the schema. TableSchema::from_module_def starts with TableId::SENTINEL until create_table assigns the real id.","commonSituations":"Code-first module tooling that defines RLS policies in the same pass as table creation; migrations reordered so the RLS step runs before table creation; module manifests deserialized from JSON/YAML that omit table_id.","solutions":["Create the table first, then copy the assigned id into the policy (row_level_security_schema.table_id = table_schema.table_id) before calling create_row_level_security","If the schema came from deserialization, resolve table_id from the table name via the committed schema","Add a debug_assert!/unit test over generated schemas asserting table_id != TableId::SENTINEL"],"exampleFix":"// before: schema built before the table exists, id never assigned\nlet rls = RowLevelSecuritySchema { table_id: TableId::SENTINEL, ..Default::default() };\ntx.create_row_level_security(rls)?;\n\n// after: create the table first and reuse its assigned id\nlet table = tx.create_table(table_schema)?;\nlet mut rls = RowLevelSecuritySchema { ..Default::default() };\nrls.table_id = table.table_id;\ntx.create_row_level_security(rls)?;","handlingStrategy":"validation","validationCode":"fn rls_schema_is_attachable(rls: &RowLevelSecuritySchema) -> bool {\n    rls.table_id != TableId::SENTINEL\n}\n\n// before creating the policy:\nassert!(rls_schema_is_attachable(&rls), \"RLS policy must reference a created table\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always derive RLS schemas from a table schema returned by create_table, never from Default","Write unit tests that walk generated module schemas and assert no system rows carry SENTINEL ids","Order migrations: create table, then policies that reference it","Treat TableId::SENTINEL anywhere outside table creation as a bug"],"tags":["rust","spacetimedb","row-level-security","schema","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"9e0d92412ff2248f401a8ad12d535f2b5ac30912","analyzedAt":"2026-08-20T06:08:37.179Z","contentChangedAt":"2026-08-20T06:08:37.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}