fix: verify shadow SQL package ownership
This commit is contained in:
@@ -6,6 +6,9 @@ SELECT CAST(SERVERPROPERTY('ProductVersion') AS varchar(30)) AS product_version,
|
||||
DB_NAME() AS database_name,
|
||||
SYSUTCDATETIME() AS checked_at_utc;
|
||||
|
||||
SELECT package_id, package_version, installed_at
|
||||
FROM alg.shadow_package_manifest;
|
||||
|
||||
SELECT schema_name(object.schema_id) AS schema_name,
|
||||
object.name AS object_name,
|
||||
object.type_desc
|
||||
|
||||
@@ -6,7 +6,20 @@
|
||||
SET NOCOUNT ON;
|
||||
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
|
||||
|
||||
IF SCHEMA_ID(N'alg') IS NULL EXEC(N'CREATE SCHEMA alg');
|
||||
IF SCHEMA_ID(N'alg') IS NOT NULL
|
||||
THROW 51000, 'FT546 shadow install refused: alg schema already exists', 1;
|
||||
|
||||
EXEC(N'CREATE SCHEMA alg');
|
||||
GO
|
||||
|
||||
CREATE TABLE alg.shadow_package_manifest (
|
||||
package_id varchar(80) NOT NULL CONSTRAINT PK_alg_shadow_package_manifest PRIMARY KEY,
|
||||
package_version varchar(40) NOT NULL,
|
||||
installed_at datetime2 NOT NULL CONSTRAINT DF_alg_shadow_package_manifest_installed_at DEFAULT SYSUTCDATETIME()
|
||||
);
|
||||
|
||||
INSERT INTO alg.shadow_package_manifest (package_id, package_version)
|
||||
VALUES ('FT546WMS_SHADOW_ONLY', '2026-08-12-1');
|
||||
GO
|
||||
|
||||
IF OBJECT_ID(N'alg.warehouse_policy', N'U') IS NULL
|
||||
|
||||
@@ -1,51 +1,103 @@
|
||||
/*
|
||||
Removes only the FT546 shadow package. Export alg.decision_log first.
|
||||
Removes only the exact FT546 shadow package version after ownership checks.
|
||||
Export alg.decision_log and stop the shadow application first.
|
||||
No Legacy WMS business row is modified by this script.
|
||||
*/
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
IF SCHEMA_ID(N'alg') IS NULL
|
||||
THROW 51010, 'FT546 shadow uninstall refused: alg schema does not exist', 1;
|
||||
|
||||
IF OBJECT_ID(N'alg.shadow_package_manifest', N'U') IS NULL
|
||||
THROW 51011, 'FT546 shadow uninstall refused: shadow package manifest does not match', 1;
|
||||
|
||||
IF (SELECT COUNT(*) FROM alg.shadow_package_manifest
|
||||
WHERE package_id = 'FT546WMS_SHADOW_ONLY'
|
||||
AND package_version = '2026-08-12-1') <> 1
|
||||
OR (SELECT COUNT(*) FROM alg.shadow_package_manifest) <> 1
|
||||
THROW 51012, 'FT546 shadow uninstall refused: shadow package manifest does not match', 1;
|
||||
|
||||
DECLARE @expected_objects TABLE (
|
||||
object_name sysname NOT NULL PRIMARY KEY,
|
||||
object_type char(2) NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO @expected_objects (object_name, object_type) VALUES
|
||||
(N'shadow_package_manifest', 'U'),
|
||||
(N'warehouse_policy', 'U'),
|
||||
(N'project_policy', 'U'),
|
||||
(N'space_role', 'U'),
|
||||
(N'space_topology', 'U'),
|
||||
(N'inventory_item_snapshot', 'U'),
|
||||
(N'space_inventory_summary', 'U'),
|
||||
(N'lane_state', 'U'),
|
||||
(N'reservation', 'U'),
|
||||
(N'deep_workflow_lock', 'U'),
|
||||
(N'decision_log', 'U'),
|
||||
(N'v_inventory_item', 'V'),
|
||||
(N'v_active_job', 'V'),
|
||||
(N'v_space_state', 'V'),
|
||||
(N'usp_rebuild_space_topology', 'P'),
|
||||
(N'usp_sync_inventory_snapshot', 'P'),
|
||||
(N'usp_compute_lane_semantic_fingerprint', 'P'),
|
||||
(N'usp_rebuild_lane_state', 'P'),
|
||||
(N'usp_refresh_dirty_lanes', 'P');
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.objects object
|
||||
LEFT JOIN @expected_objects expected
|
||||
ON expected.object_name = object.name AND expected.object_type = object.type
|
||||
WHERE object.schema_id = SCHEMA_ID(N'alg')
|
||||
AND object.is_ms_shipped = 0
|
||||
AND object.parent_object_id = 0
|
||||
AND expected.object_name IS NULL
|
||||
)
|
||||
THROW 51013, 'FT546 shadow uninstall refused: unknown object exists in alg schema', 1;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM @expected_objects expected
|
||||
LEFT JOIN sys.objects object
|
||||
ON object.schema_id = SCHEMA_ID(N'alg')
|
||||
AND object.name = expected.object_name
|
||||
AND object.type = expected.object_type
|
||||
WHERE object.object_id IS NULL
|
||||
)
|
||||
THROW 51014, 'FT546 shadow uninstall refused: expected object is missing or has wrong type', 1;
|
||||
|
||||
BEGIN TRY
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
IF OBJECT_ID(N'alg.usp_refresh_dirty_lanes', N'P') IS NOT NULL
|
||||
DROP PROCEDURE alg.usp_refresh_dirty_lanes;
|
||||
IF OBJECT_ID(N'alg.usp_rebuild_lane_state', N'P') IS NOT NULL
|
||||
DROP PROCEDURE alg.usp_rebuild_lane_state;
|
||||
IF OBJECT_ID(N'alg.usp_compute_lane_semantic_fingerprint', N'P') IS NOT NULL
|
||||
DROP PROCEDURE alg.usp_compute_lane_semantic_fingerprint;
|
||||
IF OBJECT_ID(N'alg.usp_sync_inventory_snapshot', N'P') IS NOT NULL
|
||||
DROP PROCEDURE alg.usp_sync_inventory_snapshot;
|
||||
IF OBJECT_ID(N'alg.usp_rebuild_space_topology', N'P') IS NOT NULL
|
||||
DROP PROCEDURE alg.usp_rebuild_space_topology;
|
||||
GO
|
||||
|
||||
IF OBJECT_ID(N'alg.v_space_state', N'V') IS NOT NULL
|
||||
DROP VIEW alg.v_space_state;
|
||||
IF OBJECT_ID(N'alg.v_active_job', N'V') IS NOT NULL
|
||||
DROP VIEW alg.v_active_job;
|
||||
IF OBJECT_ID(N'alg.v_inventory_item', N'V') IS NOT NULL
|
||||
DROP VIEW alg.v_inventory_item;
|
||||
GO
|
||||
|
||||
IF OBJECT_ID(N'alg.decision_log', N'U') IS NOT NULL
|
||||
DROP TABLE alg.decision_log;
|
||||
IF OBJECT_ID(N'alg.deep_workflow_lock', N'U') IS NOT NULL
|
||||
DROP TABLE alg.deep_workflow_lock;
|
||||
IF OBJECT_ID(N'alg.reservation', N'U') IS NOT NULL
|
||||
DROP TABLE alg.reservation;
|
||||
IF OBJECT_ID(N'alg.lane_state', N'U') IS NOT NULL
|
||||
DROP TABLE alg.lane_state;
|
||||
IF OBJECT_ID(N'alg.space_inventory_summary', N'U') IS NOT NULL
|
||||
DROP TABLE alg.space_inventory_summary;
|
||||
IF OBJECT_ID(N'alg.inventory_item_snapshot', N'U') IS NOT NULL
|
||||
DROP TABLE alg.inventory_item_snapshot;
|
||||
IF OBJECT_ID(N'alg.space_topology', N'U') IS NOT NULL
|
||||
DROP TABLE alg.space_topology;
|
||||
IF OBJECT_ID(N'alg.space_role', N'U') IS NOT NULL
|
||||
DROP TABLE alg.space_role;
|
||||
IF OBJECT_ID(N'alg.project_policy', N'U') IS NOT NULL
|
||||
DROP TABLE alg.project_policy;
|
||||
IF OBJECT_ID(N'alg.warehouse_policy', N'U') IS NOT NULL
|
||||
DROP TABLE alg.warehouse_policy;
|
||||
GO
|
||||
DROP TABLE alg.shadow_package_manifest;
|
||||
|
||||
IF SCHEMA_ID(N'alg') IS NOT NULL
|
||||
EXEC(N'DROP SCHEMA alg');
|
||||
COMMIT TRANSACTION;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
IF @@TRANCOUNT > 0
|
||||
ROLLBACK TRANSACTION;
|
||||
THROW;
|
||||
END CATCH;
|
||||
GO
|
||||
|
||||
@@ -14,6 +14,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ShadowSqlServerMigrationScriptTest {
|
||||
private static final Set<String> ALLOWED_INSTALL_OBJECTS = Set.of(
|
||||
"table alg.shadow_package_manifest",
|
||||
"table alg.warehouse_policy",
|
||||
"table alg.project_policy",
|
||||
"table alg.space_role",
|
||||
@@ -111,6 +112,36 @@ class ShadowSqlServerMigrationScriptTest {
|
||||
assertThat(normalize(resource("db/sqlserver/diagnostics-shadow.sql"))).contains(readUncommitted);
|
||||
}
|
||||
|
||||
@Test
|
||||
void installRefusesAnyExistingAlgorithmSchemaAndCreatesExactManifest() throws IOException {
|
||||
String install = normalize(resource("db/sqlserver/install-shadow.sql"));
|
||||
|
||||
assertThat(install).contains(
|
||||
"if schema_id(n'alg') is not null",
|
||||
"throw 51000",
|
||||
"create table alg.shadow_package_manifest",
|
||||
"ft546wms_shadow_only",
|
||||
"2026-08-12-1");
|
||||
assertThat(install.indexOf("throw 51000")).isLessThan(install.indexOf("create schema alg"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void uninstallValidatesManifestAndObjectAllowListBeforeAnyDrop() throws IOException {
|
||||
String uninstall = normalize(resource("db/sqlserver/uninstall-shadow.sql"));
|
||||
int firstDrop = uninstall.indexOf("drop procedure");
|
||||
|
||||
assertThat(uninstall).contains(
|
||||
"ft546wms_shadow_only",
|
||||
"2026-08-12-1",
|
||||
"shadow package manifest does not match",
|
||||
"unknown object exists in alg schema",
|
||||
"object.parent_object_id = 0",
|
||||
"begin transaction",
|
||||
"rollback transaction");
|
||||
assertThat(uninstall.indexOf("shadow package manifest does not match")).isLessThan(firstDrop);
|
||||
assertThat(uninstall.indexOf("unknown object exists in alg schema")).isLessThan(firstDrop);
|
||||
}
|
||||
|
||||
private Set<String> createdObjects(String sql) {
|
||||
return objects(sql, Pattern.compile(
|
||||
"(?i)create\\s+(table|view|procedure|trigger)\\s+([a-z0-9_]+\\.[a-z0-9_]+)"));
|
||||
|
||||
Reference in New Issue
Block a user