keiro-ops 0.16.0.0 → 0.17.0.0
raw patch · 4 files changed
+156/−70 lines, 4 filesdep ~keirodep ~keiro-migrationsdep ~keiro-pgmqPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: keiro, keiro-migrations, keiro-pgmq, keiro-test-support, pgmq-migration
API changes (from Hackage documentation)
Files
- CHANGELOG.md +14/−0
- keiro-ops.cabal +80/−67
- src/Keiro/Ops/Pgmq.hs +12/−3
- test/Main.hs +50/−0
CHANGELOG.md view
@@ -6,6 +6,20 @@ ## [Unreleased] +## 0.17.0.0 — 2026-09-17++### Breaking Changes++- Forced `pgmq dlq purge` execution now calls the visibility-guarded library API,+ reports the actual deleted count, and fails without deleting when metrics show+ hidden rows from an inspection. The global `--force` flag still authorizes the+ mutation after preview; it does not bypass this safety refusal.++### Other Changes++- No user-facing library changes. The test-suite's `pgmq-migration` bound moves+ from `>=0.5 && <0.6` to `>=0.6 && <0.7` with the PGMQ 0.6 adoption.+ ## 0.16.0.0 — 2026-09-07 ### Other Changes
keiro-ops.cabal view
@@ -1,34 +1,39 @@-cabal-version: 3.0-name: keiro-ops-version: 0.16.0.0-synopsis: Operational command-line interface for Keiro deployments+cabal-version: 3.0+name: keiro-ops+version: 0.17.0.0+synopsis: Operational command-line interface for Keiro deployments description: A standalone database-only console and embeddable command tree for inspecting and operating Keiro deployments. -license: BSD-3-Clause-license-file: LICENSE-author: Nadeem Bitar-maintainer: nadeem@gmail.com-copyright: 2026 Nadeem Bitar-category: Operations-homepage: https://github.com/shinzui/keiro#readme-bug-reports: https://github.com/shinzui/keiro/issues-build-type: Simple-tested-with: GHC >=9.12 && <9.13+license: BSD-3-Clause+license-file: LICENSE+author: Nadeem Bitar+maintainer: nadeem@gmail.com+copyright: 2026 Nadeem Bitar+category: Operations+homepage: https://github.com/shinzui/keiro#readme+bug-reports: https://github.com/shinzui/keiro/issues+build-type: Simple+tested-with: ghc >=9.12 && <9.13 extra-doc-files: CHANGELOG.md source-repository head- type: git+ type: git location: https://github.com/shinzui/keiro.git common warnings ghc-options:- -Wall -Wcompat -Widentities -Wincomplete-record-updates- -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints+ -Wall+ -Wcompat+ -Widentities+ -Wincomplete-record-updates+ -Wincomplete-uni-patterns+ -Wpartial-fields+ -Wredundant-constraints common shared- default-language: GHC2024+ default-language: GHC2024 default-extensions: BlockArguments DeriveAnyClass@@ -40,8 +45,8 @@ OverloadedStrings library- import: warnings, shared- hs-source-dirs: src+ import: warnings, shared+ hs-source-dirs: src exposed-modules: Keiro.Ops Keiro.Ops.Embed@@ -61,58 +66,66 @@ Keiro.Ops.Workflow build-depends:- , aeson >=2.2.2 && <2.3- , base >=4.21 && <5- , bytestring >=0.12 && <0.13- , containers >=0.6 && <0.8- , effectful >=2.6 && <2.7- , effectful-core >=2.6 && <2.7- , hasql >=1.10 && <1.11- , keiro ^>=0.16.0.0- , keiro-migrations ^>=0.16.0.0- , keiro-pgmq ^>=0.16.0.0- , kiroku-store >=0.8 && <0.9- , optparse-applicative >=0.18 && <0.20- , text >=2.1 && <2.2- , time >=1.12 && <1.15- , uuid >=1.3 && <1.4- , vector >=0.13 && <0.14+ aeson >=2.2.2 && <2.3,+ base >=4.21 && <5,+ bytestring >=0.12 && <0.13,+ containers >=0.6 && <0.8,+ effectful >=2.6 && <2.7,+ effectful-core >=2.6 && <2.7,+ hasql >=1.10 && <1.11,+ keiro ^>=0.17.0.0,+ keiro-migrations ^>=0.17.0.0,+ keiro-pgmq ^>=0.17.0.0,+ kiroku-store >=0.8 && <0.9,+ optparse-applicative >=0.18 && <0.20,+ text >=2.1 && <2.2,+ time >=1.12 && <1.15,+ uuid >=1.3 && <1.4,+ vector >=0.13 && <0.14, executable keiro-ops- import: warnings, shared+ import: warnings, shared hs-source-dirs: app- main-is: Main.hs- ghc-options: -threaded -rtsopts -with-rtsopts=-N+ main-is: Main.hs+ ghc-options:+ -threaded+ -rtsopts+ -with-rtsopts=-N+ build-depends:- , base >=4.21 && <5- , keiro-ops+ base >=4.21 && <5,+ keiro-ops, test-suite keiro-ops-test- import: warnings, shared- type: exitcode-stdio-1.0- hs-source-dirs: test- main-is: Main.hs- ghc-options: -threaded -rtsopts -with-rtsopts=-N+ import: warnings, shared+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Main.hs+ ghc-options:+ -threaded+ -rtsopts+ -with-rtsopts=-N+ build-tool-depends: keiro-ops:keiro-ops build-depends:- , aeson >=2.2 && <2.3- , base >=4.21 && <5- , bytestring >=0.12 && <0.13- , containers >=0.6 && <0.8- , effectful >=2.6 && <2.7- , effectful-core >=2.6 && <2.7- , hasql >=1.10 && <1.11- , hasql-transaction >=1.1 && <1.3- , hspec >=2.11- , keiro ^>=0.16.0.0- , keiro-ops- , keiro-pgmq ^>=0.16.0.0- , keiro-test-support ^>=0.16.0.0- , kiroku-store >=0.8 && <0.9- , optparse-applicative >=0.18 && <0.20- , pgmq-migration >=0.5 && <0.6- , process >=1.6 && <1.7- , text >=2.1 && <2.2- , time >=1.12 && <1.15- , uuid >=1.3 && <1.4- , vector >=0.13 && <0.14+ aeson >=2.2 && <2.3,+ base >=4.21 && <5,+ bytestring >=0.12 && <0.13,+ containers >=0.6 && <0.8,+ effectful >=2.6 && <2.7,+ effectful-core >=2.6 && <2.7,+ hasql >=1.10 && <1.11,+ hasql-transaction >=1.1 && <1.3,+ hspec >=2.11,+ keiro ^>=0.17.0.0,+ keiro-ops,+ keiro-pgmq ^>=0.17.0.0,+ keiro-test-support ^>=0.17.0.0,+ kiroku-store >=0.8 && <0.9,+ optparse-applicative >=0.18 && <0.20,+ pgmq-migration >=0.6 && <0.7,+ process >=1.6 && <1.7,+ text >=2.1 && <2.2,+ time >=1.12 && <1.15,+ uuid >=1.3 && <1.4,+ vector >=0.13 && <0.14,
src/Keiro/Ops/Pgmq.hs view
@@ -127,8 +127,15 @@ | env.force = do confirmed <- confirmPurge env queue if confirmed- then handlePgmq (runJobEff (pgmqRuntime env) (purgeDlq (rawJob queue))) $ \() ->- Succeeded (messageResult ("purged DLQ for " <> queue))+ then handlePgmq (runJobEff (pgmqRuntime env) (purgeDlq (rawJob queue))) \case+ PurgeDlqBlocked hiddenCount ->+ Failed+ ( "DLQ purge refused because "+ <> showText hiddenCount+ <> " row(s) are hidden; archive inspected ids or wait for visibility to expire"+ )+ PurgeDlqPurged purged ->+ Succeeded (mutationCountResult "purge" queue purged) else pure (Failed "queue-name confirmation did not match; DLQ purge cancelled") | otherwise = previewFromDepth env "purge" queue Nothing ["pgmq", "dlq", "purge", "--queue", queue] @@ -174,6 +181,7 @@ { jobName = queue, jobQueue = queueRef queue, jobCodec = aesonJobCodec,+ jobOrdering = Unordered, jobPolicy = defaultRetryPolicy } @@ -206,10 +214,11 @@ "original_message_id" .= entry.originalMessageId, "original_enqueued_at" .= entry.originalEnqueuedAt, "read_count" .= entry.readCount,+ "original_headers" .= entry.originalHeaders, "raw_body" .= entry.rawBody ] -mutationCountResult :: Text -> Text -> Int -> OpsResult+mutationCountResult :: (Show count, Aeson.ToJSON count) => Text -> Text -> count -> OpsResult mutationCountResult operation queue count = OpsResult { headers = ["operation", "queue", "affected"],
test/Main.hs view
@@ -1159,9 +1159,58 @@ purged <- OpsPgmq.runCommand (opsEnv True store) (OpsPgmq.Dlq (OpsPgmq.Purge queue)) purged `shouldSatisfy` isSucceeded+ jsonInteger "affected" purged `shouldBe` Just 1 (_, dlqAfterPurge) <- depths dlqAfterPurge `shouldBe` 0 + it "refuses purge after inspection and leaves the DLQ unchanged" $ \store -> do+ let queue = "keiro_ops_test.purge_hidden"+ job = rawValueJob queue+ runPgmqUnit action = do+ result <- runJobEff (JobRuntime store.pool Nothing) action+ either (fail . show) pure result+ dlqDepth = do+ result <- runJobEff (JobRuntime store.pool Nothing) (jobDlqMetrics job)+ metrics <- either (fail . show) pure result+ pure metrics.queueLength+ inspectedCount <- runPgmqUnit $ do+ ensureJobQueue job+ _ <- enqueue job (object ["kind" .= ("inspect-first" :: Text)])+ _ <- runJobOnce 1 job (\_ -> pure (Dead "bad"))+ inspected <- readDlq job 1+ pure (length inspected)+ inspectedCount `shouldBe` 1++ outcome <- OpsPgmq.runCommand (opsEnv True store) (OpsPgmq.Dlq (OpsPgmq.Purge queue))+ case outcome of+ Failed detail -> do+ detail `shouldSatisfy` Text.isInfixOf "1 row(s) are hidden"+ detail `shouldSatisfy` Text.isInfixOf "archive inspected ids"+ other -> expectationFailure ("expected guarded purge refusal, got " <> show other)+ dlqDepth `shouldReturn` 1++ it "archives an inspected entry by id while it remains hidden" $ \store -> do+ let queue = "keiro_ops_test.archive_hidden"+ job = rawValueJob queue+ runPgmqUnit action = do+ result <- runJobEff (JobRuntime store.pool Nothing) action+ either (fail . show) pure result+ dlqDepth = do+ result <- runJobEff (JobRuntime store.pool Nothing) (jobDlqMetrics job)+ metrics <- either (fail . show) pure result+ pure metrics.queueLength+ inspectedCount <- runPgmqUnit $ do+ ensureJobQueue job+ _ <- enqueue job (object ["kind" .= ("retain" :: Text)])+ _ <- runJobOnce 1 job (\_ -> pure (Dead "bad"))+ inspected <- readDlq job 1+ pure (length inspected)+ inspectedCount `shouldBe` 1++ archived <- OpsPgmq.runCommand (opsEnv True store) (OpsPgmq.Dlq (OpsPgmq.Archive queue (Just 1) 100))+ archived `shouldSatisfy` isSucceeded+ dlqDepth `shouldReturn` 0+ describe "projection handlers" $ around (withFreshStore fixture) do it "prunes only the named dedup rows" $ \store -> do _ <- seedKirokuEvent store "projection-source" "018f5f43-8a70-7b9a-9a9b-59d391a76810" Nothing@@ -1360,6 +1409,7 @@ { jobName = name, jobQueue = queueRef name, jobCodec = aesonJobCodec,+ jobOrdering = Unordered, jobPolicy = defaultRetryPolicy }