diff --git a/.secrets.baseline b/.secrets.baseline index 6a7ce84b..f3cc2223 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -324,637 +324,665 @@ "filename": "app/internal/server/api/server.gen.go", "hashed_secret": "9fd0aaae1a3d0bc789d081307161ea9a821f9dee", "is_verified": false, - "line_number": 721 + "line_number": 773 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "12c33f5bf2d3e44c93f13dc4d0ae06cfb789e39e", + "hashed_secret": "27d043f95f1559d4082ee182db00c0b9341ed929", "is_verified": false, - "line_number": 2541 + "line_number": 2593 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "48688c31fd250c9203a9694c5e0e5f1e7a92ecc0", + "hashed_secret": "8aaf49f4614e56378a639a8acf12fee40c611fcb", "is_verified": false, - "line_number": 2542 + "line_number": 2594 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "14b59fc7ce8be2d456a5df79704a14e24103b6ce", + "hashed_secret": "1fa8c97cf4ffc61555b41cbccd8cd0e8dc3956e6", "is_verified": false, - "line_number": 2543 + "line_number": 2595 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "0323b9f0be136671be004752c9e78930ce727259", + "hashed_secret": "04994d6bc1fc812832d98f33a55aef7b4ba79186", "is_verified": false, - "line_number": 2544 + "line_number": 2596 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "34eef5ccec09437ba86c972aa016cadafa0bf36a", + "hashed_secret": "cf40c6e4ae1091379a6959ceb90d051657024894", "is_verified": false, - "line_number": 2545 + "line_number": 2597 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "4e272a1976f21d979bf3ea8cbb4e32212b7f1858", + "hashed_secret": "d4caa37b101073e0722f33c497beff9eb6f00855", "is_verified": false, - "line_number": 2546 + "line_number": 2598 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "b6cbce868ddefbe915fa342e63bed88bb3bae691", + "hashed_secret": "8627632e75ca89cbaa7d8b1a5a6d1578b195855b", "is_verified": false, - "line_number": 2547 + "line_number": 2599 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "e5c8a06c07f4076fe256d3d34a38cbc57192adc2", + "hashed_secret": "44473d6aa4b9494113bbe8da737334b0f3d34180", "is_verified": false, - "line_number": 2548 + "line_number": 2600 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "174e69dfb4dcb1bc9a8b1e8b8795996d1a89042d", + "hashed_secret": "0a6bcd7d539115a9e123d40ca73243ea3d4bfdc7", "is_verified": false, - "line_number": 2549 + "line_number": 2601 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "00aaa1842f451f75414f8a727a07ff2eb8695925", + "hashed_secret": "7ee6d746e1b1c6a7cb072a8f026b6fda1f06b0bd", "is_verified": false, - "line_number": 2550 + "line_number": 2602 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "c94156612d115ea929eec2e998c0d04c8060cf29", + "hashed_secret": "c60b16cfd6447d61565c48e33cf6604a56e204b7", "is_verified": false, - "line_number": 2551 + "line_number": 2603 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "d817c0f562ebe56fc179cc681ce4541b5ebd9e04", + "hashed_secret": "eb3b0d11bef554977009cdc0da44d803b4236247", "is_verified": false, - "line_number": 2552 + "line_number": 2604 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "97c7613a07d59cdcfd67d0edf4938070a40df819", + "hashed_secret": "468aa8f9df5ff2deef2f8a37d5ec9d5b0a0d1d2e", "is_verified": false, - "line_number": 2553 + "line_number": 2605 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "522dd73981cffb89f6a8a4b124d4dee1ea4588d0", + "hashed_secret": "08ec5a6c25417d2583b66c4e53b30a726648b2fd", "is_verified": false, - "line_number": 2554 + "line_number": 2606 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "d3cc3755e80e5cf83aa4e0a8d154465b4311b712", + "hashed_secret": "bd40e1bf4d7e521917f84608fabf395fc30613a1", "is_verified": false, - "line_number": 2555 + "line_number": 2607 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "5c6c1437a64fdab2e740bd5277f8c8aa2cc48982", + "hashed_secret": "c084473a31dd952807852b82d08e925486b6b53f", "is_verified": false, - "line_number": 2556 + "line_number": 2608 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "96883ef5fa530fcfbfcfbe5df9b3505736e65fe3", + "hashed_secret": "c72359aa7716b1601dda5fb535522af2db33560c", "is_verified": false, - "line_number": 2557 + "line_number": 2609 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "c6eb065270536b04858ec407681dc39df36faf12", + "hashed_secret": "2d77e4db82a2152429171811900c6024a4c67f7a", "is_verified": false, - "line_number": 2558 + "line_number": 2610 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "28cc49f4c8abbcb4c3b630b9d84b474b5a0c0b6c", + "hashed_secret": "9b951be7edd064ed9090afe1f0cb656319915d28", "is_verified": false, - "line_number": 2559 + "line_number": 2611 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "a0f3cb24e3fd181aafceedda8ffbdc23c07a1a59", + "hashed_secret": "3240e5f1306caa79b612383b2df47b8dcac4f34f", "is_verified": false, - "line_number": 2560 + "line_number": 2612 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "12e7e179520ff6ea03439ae5ad723dd8e9a7bcb2", + "hashed_secret": "ebdce38fe37f3517d619eb5b6de1042756099fd1", "is_verified": false, - "line_number": 2561 + "line_number": 2613 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "87b02c1279245d59f6ad4a77efdf70ef8ccbcca1", + "hashed_secret": "1c1fbc9650f7de469e54c079b705f81d8c4da0f6", "is_verified": false, - "line_number": 2562 + "line_number": 2614 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "13ab448c20dc11625c890c7cf0b8e649946e639e", + "hashed_secret": "e459ba2de45cfffdbb2c575f2df04f820094744a", "is_verified": false, - "line_number": 2563 + "line_number": 2615 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "ccaf38ccf2666028bea674c650ff3dbf473bd894", + "hashed_secret": "8e8fec073d19eedfcef33b30e53870136d9134e9", "is_verified": false, - "line_number": 2564 + "line_number": 2616 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "202eea6183f5865c7af9bd3439c4d220333faa74", + "hashed_secret": "9f2a076f53a086894fcdb9bce644820e82eb9909", "is_verified": false, - "line_number": 2565 + "line_number": 2617 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "09ad08d0d25bb90fee650c2b73f7b3e40855101e", + "hashed_secret": "a272c57dfe79b09b8badcb76e2f5e9e64a9bc0a5", "is_verified": false, - "line_number": 2566 + "line_number": 2618 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "7dd7ad4897877552b86e19dedbc861e9cb7ce87b", + "hashed_secret": "eb55bedde5a639141cb5a124d155752fc1997d55", "is_verified": false, - "line_number": 2567 + "line_number": 2619 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "28cfd8686415af8633e6f14d00f9de90e4119324", + "hashed_secret": "eb5a34a304dfea466e48c548c77ab95b3fa64e5a", "is_verified": false, - "line_number": 2568 + "line_number": 2620 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "464b4fd869152b99f4e45d4bfbd7ff3d6a38114b", + "hashed_secret": "baed435ffaee32dde87cfbd88e0de8f34e7b5da9", "is_verified": false, - "line_number": 2569 + "line_number": 2621 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "0db77bf4b7c98e1e6a8ff777e885e338254bbeb9", + "hashed_secret": "bf70b5d8689bf55a63cbc310c7898f716cc02feb", "is_verified": false, - "line_number": 2570 + "line_number": 2622 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "40002b98443b40286a9a90ce54368e1a1cd5c0da", + "hashed_secret": "0423d9bf1b8cf149f850fee888a1fff7a1e5f925", "is_verified": false, - "line_number": 2571 + "line_number": 2623 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "3b98f7f3cdad1630687f6925b41028193382badd", + "hashed_secret": "97e7646a4c35291c5103aa14baae05835e73fc88", "is_verified": false, - "line_number": 2572 + "line_number": 2624 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "b2753a9ea2a241c5b2fb5ec69ddc2918c0f387b9", + "hashed_secret": "438703e1fca79ec2d1609608548194398e28217a", "is_verified": false, - "line_number": 2573 + "line_number": 2625 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "24f0fe36c5d45f39e858c2e84ad8a3fb2e90ce1b", + "hashed_secret": "d36275869fa21512062ab663526942a8c8c102d4", "is_verified": false, - "line_number": 2574 + "line_number": 2626 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "54b1ae6dfcd3b1c761f5f1000eca8a9558eeee18", + "hashed_secret": "868cf380958e763a4ba99992e7913f44f6bc8674", "is_verified": false, - "line_number": 2575 + "line_number": 2627 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "7f910e2f72a532403c3b1d6d0d1f726302ab3f67", + "hashed_secret": "d1ac866a4361261c5ae115b85ad0f1f3ec7cde30", "is_verified": false, - "line_number": 2576 + "line_number": 2628 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "f9ec4d81dda751b177262f8db20ee4d34d862c50", + "hashed_secret": "b8fd85420b4b28233e641df46f6d5fda79850e0f", "is_verified": false, - "line_number": 2577 + "line_number": 2629 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "1a222f925635194d7f9a65a0799b776526cd4a84", + "hashed_secret": "507ba204e4f38e8e957d69e2be63a10e9afd8d15", "is_verified": false, - "line_number": 2578 + "line_number": 2630 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "3bef156047128444e378b656ca2f7e8058c6129d", + "hashed_secret": "a59f9b2f8c4def30c89a8799e29d98a34b0423f3", "is_verified": false, - "line_number": 2579 + "line_number": 2631 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "226c409907bbdf3bc8139a3bf876e88a8db7bebe", + "hashed_secret": "b0ae0757e56ef309b79bbbe3d75b56037b856886", "is_verified": false, - "line_number": 2580 + "line_number": 2632 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "d6dca4f92172258e6da80992cc0fc747d7cdf83a", + "hashed_secret": "eea07c6d7d65e8832054c8cb586da346ff774d59", "is_verified": false, - "line_number": 2581 + "line_number": 2633 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "491ae906a79f9e71757a32749982a872f5852d66", + "hashed_secret": "ad709ea0932317a490c96f815f377eb35c8a5dce", "is_verified": false, - "line_number": 2582 + "line_number": 2634 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "fcc623859a0c4c55d3de7a08024ac75ea87fb588", + "hashed_secret": "476671a9922092107320d31ff96542f7ce604275", "is_verified": false, - "line_number": 2583 + "line_number": 2635 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "59770a42338a0531bf6b52f077fb583a6559165e", + "hashed_secret": "cba23a411198f4b12dbb06cf7092d62ea6933418", "is_verified": false, - "line_number": 2584 + "line_number": 2636 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "eab7e66517359507daa75ac8c8d579cd87865a75", + "hashed_secret": "c0be1d9f3aaa966ab468c125e2aadf4c1d305002", "is_verified": false, - "line_number": 2585 + "line_number": 2637 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "0cfc9de1d457aad682a8df7af414deb2a33f8223", + "hashed_secret": "3e310bb68a7cbf346404f18788761e4f7a3d24a7", "is_verified": false, - "line_number": 2586 + "line_number": 2638 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "b80a03883d673b1ac2642c2ffd2953b32ffef377", + "hashed_secret": "4dbd8baf97a14f1c6db84dfc70e5de1767d62077", "is_verified": false, - "line_number": 2587 + "line_number": 2639 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "4004fd784d798dc99deea691c083d640ae497ff4", + "hashed_secret": "b613fe7dc0d4c9098a1fec7850fcaa26044cca5e", "is_verified": false, - "line_number": 2588 + "line_number": 2640 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "ff5ffc49943af0757c1617c296f7be4c238cdc80", + "hashed_secret": "f16c78daf0ac517f7748c8ab1f30b84dc0f9d276", "is_verified": false, - "line_number": 2589 + "line_number": 2641 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "fc2248e35eca67e192fa6d76ffe4017347d82b8e", + "hashed_secret": "a3d66601a23bed04fade9ef8b31f19ca72473396", "is_verified": false, - "line_number": 2590 + "line_number": 2642 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "d4189c02d5c475405cb0ee25af5c551c6e7f777f", + "hashed_secret": "43359088e4f850ac11befef65f4b978afd986952", "is_verified": false, - "line_number": 2591 + "line_number": 2643 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "5343c2c3351cdbe0e12c17da46b360695adf4d90", + "hashed_secret": "e82736dca7d0707ddb791e8250f4ade7a7043d89", "is_verified": false, - "line_number": 2592 + "line_number": 2644 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "91c05e01d118930dea69aaa0a4b5e4a0d2026f15", + "hashed_secret": "9fcdbeb231ad9d08b6de3a8e6755e4ab08c9e5ee", "is_verified": false, - "line_number": 2593 + "line_number": 2645 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "dff7a5548f39ebf9099d8e2fd4cde7bc1912cc4c", + "hashed_secret": "5105d598f50275d9bf5eec4f7a97d5d6b485c099", "is_verified": false, - "line_number": 2594 + "line_number": 2646 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "2539ab852fec8fe0072e06f68d6e8d83e499ec65", + "hashed_secret": "78c6087df9d9590a7a03bea749a777586a80eb5c", "is_verified": false, - "line_number": 2595 + "line_number": 2647 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "ebe60700d51452a0abda5069f813579bf4b34436", + "hashed_secret": "13271144ea3a69450417f7031b7111efd5bb712e", "is_verified": false, - "line_number": 2596 + "line_number": 2648 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "919dba66dae9c6f0c05e4140dec9df12cbdfd15a", + "hashed_secret": "2008ebccdb02777c65a2edbb2a1c090f420d4dcc", "is_verified": false, - "line_number": 2597 + "line_number": 2649 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "ab65ef184befc10ad1e295ae966b9f1e529bef91", + "hashed_secret": "3171968b7aecb98c3b8bdbc9ce6349a3c97b11d7", "is_verified": false, - "line_number": 2598 + "line_number": 2650 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "7c777967a551a9d1247ef3fd0ace3e1fcf7b2796", + "hashed_secret": "08d80e4e316531972d055243f7c2cc03152f9e56", "is_verified": false, - "line_number": 2599 + "line_number": 2651 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "eba4f68f41c881b0ee713b66770da6cc2f734478", + "hashed_secret": "e4621666c61dbdbdcf7f3a72dcb706e4ce9ac6e6", "is_verified": false, - "line_number": 2600 + "line_number": 2652 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "bdcd610e829ac1df5225fc9fd4fd212e871b7607", + "hashed_secret": "bcf3c6a81cececfe435a0ac65d8e2d99c0856a3c", "is_verified": false, - "line_number": 2601 + "line_number": 2653 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "1b354f89f529b23f581e816e20d5d8f6a33db36f", + "hashed_secret": "6c263d2c34dc4ba26ed021b7a55c161d2b577ea9", "is_verified": false, - "line_number": 2602 + "line_number": 2654 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "2730785f97c943d0e071bf69240b453dcf0bf381", + "hashed_secret": "d0f2e720f7f63fc6159a70f3f7f15ef46ca63ce7", "is_verified": false, - "line_number": 2603 + "line_number": 2655 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "a84725c7b44df55bfcf0b341f35b468de0131997", + "hashed_secret": "9b9af431a5a5db659bad1a8dc255962151d3f4e9", "is_verified": false, - "line_number": 2604 + "line_number": 2656 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "3cd04287c626656914df8b8d8c01682b9fc797e7", + "hashed_secret": "083a0f0a80081cd6245e549d25d57ba17c1293bb", "is_verified": false, - "line_number": 2605 + "line_number": 2657 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "8b3d29b8a8fc07c74fc887ec421c36f1600c1b78", + "hashed_secret": "6a9faec852dd1628414439ae6bf7245352c5e2a8", "is_verified": false, - "line_number": 2606 + "line_number": 2658 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "617be33888d7beffb54a6fd57bc5d2a94e3fde76", + "hashed_secret": "127da64c54b938fe9607719e968292c0724f2b74", "is_verified": false, - "line_number": 2607 + "line_number": 2659 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "99518344c40e6e24d433d6e3b4bb45b4ec29bc82", + "hashed_secret": "24673a96834b485278c75b25e72bbbb04b9fa4e5", "is_verified": false, - "line_number": 2608 + "line_number": 2660 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "d43fd08d5b9426b3089427cd55695c8ed0d7d086", + "hashed_secret": "9fdb059046914f2d86588ed0bc942009bd89bbcc", "is_verified": false, - "line_number": 2609 + "line_number": 2661 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "527a6849acc19f5fac671c0abf29bef45e9627cc", + "hashed_secret": "6d1fdd2ec0d4363ab2aa8481af253365616b30d7", "is_verified": false, - "line_number": 2610 + "line_number": 2662 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "95e2c7adb22252e9205c95adc6d3d5bc866b2d53", + "hashed_secret": "9897a94f3241124a7d06ab6fff79ec85607cab9e", "is_verified": false, - "line_number": 2611 + "line_number": 2663 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "c94d968d3cb368ea61f46cb5d7c039ad6390111a", + "hashed_secret": "e78131100cd450bab65b40f03c915af7dfbb4d40", "is_verified": false, - "line_number": 2612 + "line_number": 2664 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "7b399e9ba4a1d7a85226689b48176ce1d96a532a", + "hashed_secret": "378e8c7fedb4095a05aab0269c4fd1aef23a4ad4", "is_verified": false, - "line_number": 2613 + "line_number": 2665 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "9967dc41bd231f414264b2959562245a7ebb7c2c", + "hashed_secret": "f94570d6d8e72d807bc161c3795dfe235004656d", "is_verified": false, - "line_number": 2614 + "line_number": 2666 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "da34bd9c27a4aa0b8c19f1d04f016eeac85de377", + "hashed_secret": "34527ee297fe5aecae33d513eb6b0312df57450b", "is_verified": false, - "line_number": 2615 + "line_number": 2667 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "9af5b0d206f70d73a4a8fc4a6d40c96b55d78326", + "hashed_secret": "0f53082bd6f5a77a0009dcbd258964eaa286af06", "is_verified": false, - "line_number": 2616 + "line_number": 2668 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "2a1161144ef7c3d7161a7efdf81e8cb1177386b4", + "hashed_secret": "9dc1844a042e4b4e683e3a7ad7bc9b75bbd41ac5", "is_verified": false, - "line_number": 2617 + "line_number": 2669 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "ed1ebcec2fc08abe3d16838d6673da6f0fd88c44", + "hashed_secret": "7e2845d0b934ce015d62d9b27986cadafa0361ef", "is_verified": false, - "line_number": 2618 + "line_number": 2670 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "ec7925e69dcbaa856e3c64b7516cc50c5b0c6b54", + "hashed_secret": "d9a781b873ca25b3931ed16417310dab2ef11d9f", "is_verified": false, - "line_number": 2619 + "line_number": 2671 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "79c4330781bace65c320f4339fc04c0a981e5341", + "hashed_secret": "a16508d02f8e793d703aa540c69031b8eb06c0f3", "is_verified": false, - "line_number": 2620 + "line_number": 2672 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "88a6ea1e1374be512a1f77208df7b25c7f1623f9", + "hashed_secret": "b819094361b037640d1b4ee74e121b8d51df9963", "is_verified": false, - "line_number": 2621 + "line_number": 2673 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "cdca2f433458500a867360ad00349a088eb78668", + "hashed_secret": "0ada40ec31b19b06c65de38946e2ba5cd2642a29", "is_verified": false, - "line_number": 2622 + "line_number": 2674 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "c0f48e7b7bb6c5b9c56dfaa0d86c8a5ab4571419", + "hashed_secret": "3073df1564642739b9858b558c8ef9f4d77c960c", "is_verified": false, - "line_number": 2623 + "line_number": 2675 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "00322738ee78c49f8ac9c2a414778b5809be21d4", + "hashed_secret": "0b97ca091d7a7823251bf004e27b5813fb89e707", "is_verified": false, - "line_number": 2624 + "line_number": 2676 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "b17dc267efb976fb3ee7240b395ee672e06fc8b8", + "hashed_secret": "4ab59bea132e64b0b33da7fce5ae59ff2b4f41c3", "is_verified": false, - "line_number": 2625 + "line_number": 2677 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "dbc59a109e6ff19f227f5be80ee97f28da11af57", + "hashed_secret": "13900c6388ce3550f68af8a424e5b42770b4827a", "is_verified": false, - "line_number": 2626 + "line_number": 2678 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "1bb9b56d7a8a2c157cf963685c228f09101e0a8f", + "hashed_secret": "5a1bf188f3581e93d06401a05928ddd4350359d6", "is_verified": false, - "line_number": 2627 + "line_number": 2679 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "ac370b1f37ca3ec9b256e2135866e2acb7408ef5", + "hashed_secret": "99967bcd7dad9752144900c9933992a9f00d7849", "is_verified": false, - "line_number": 2628 + "line_number": 2680 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "b8655bc6ae6140353dd2d7b0e19fcbd85d12497c", + "hashed_secret": "898c84ee78f2d284431c25a04b358cc14b219f56", "is_verified": false, - "line_number": 2629 + "line_number": 2681 }, { "type": "Base64 High Entropy String", "filename": "app/internal/server/api/server.gen.go", - "hashed_secret": "4b70cc728847c8cf1c4af02c423934295736d2aa", + "hashed_secret": "dc4b14d70e9ee8cc5543bd1fc9360ebdea16fc88", "is_verified": false, - "line_number": 2630 + "line_number": 2682 + }, + { + "type": "Base64 High Entropy String", + "filename": "app/internal/server/api/server.gen.go", + "hashed_secret": "10d8867c4a31304be6df931705d38d32cf55f757", + "is_verified": false, + "line_number": 2683 + }, + { + "type": "Base64 High Entropy String", + "filename": "app/internal/server/api/server.gen.go", + "hashed_secret": "a651db43622ba401c23381ce03f4e3a13c308e89", + "is_verified": false, + "line_number": 2684 + }, + { + "type": "Base64 High Entropy String", + "filename": "app/internal/server/api/server.gen.go", + "hashed_secret": "729d97229c5dae2e99fd1cc6c7890655eda7a54e", + "is_verified": false, + "line_number": 2685 + }, + { + "type": "Base64 High Entropy String", + "filename": "app/internal/server/api/server.gen.go", + "hashed_secret": "f0c61e68ec18d2bffba5e3236ac22866a28160b7", + "is_verified": false, + "line_number": 2686 } ], "app/internal/server/credentials_handlers.go": [ @@ -1349,5 +1377,5 @@ } ] }, - "generated_at": "2026-05-29T22:04:55Z" + "generated_at": "2026-05-30T01:34:00Z" } diff --git a/app/api/openapi.yaml b/app/api/openapi.yaml index ebb44612..59cb6854 100644 --- a/app/api/openapi.yaml +++ b/app/api/openapi.yaml @@ -686,16 +686,16 @@ paths: /api/v1/hosts/{id}: get: operationId: getHostByID - summary: Fetch a host + summary: Fetch a host with liveness + compliance enrichment x-required-permission: host:read parameters: - {name: id, in: path, required: true, schema: {type: string, format: uuid}} responses: '200': - description: Host + description: Host detail (includes liveness + compliance_summary) content: application/json: - schema: {$ref: '#/components/schemas/HostResponse'} + schema: {$ref: '#/components/schemas/HostDetailResponse'} '404': description: Host not found content: @@ -1091,6 +1091,47 @@ components: created_at: {type: string, format: date-time} updated_at: {type: string, format: date-time} + # Per-host enrichment (spec api-hosts v1.1.0 C-05). + # Populated on GET /hosts/{id} when host_liveness has a row for the host. + HostLiveness: + type: object + required: [reachability_status] + properties: + reachability_status: + type: string + enum: [reachable, unreachable, unknown] + last_probe_at: {type: string, format: date-time, nullable: true} + last_response_ms: {type: integer, nullable: true} + consecutive_failures: {type: integer} + last_state_change_at: {type: string, format: date-time, nullable: true} + last_error_type: {type: string, nullable: true} + + # Per-host compliance roll-up (spec api-hosts v1.1.0 C-06). + # Always populated on GET /hosts/{id} — zero counts when no rule_state. + HostComplianceSummary: + type: object + required: [passing, failing, skipped, error, total] + properties: + passing: {type: integer, format: int64} + failing: {type: integer, format: int64} + skipped: {type: integer, format: int64} + error: {type: integer, format: int64} + total: {type: integer, format: int64} + + # Detail-view response: HostResponse plus enrichments. Used by + # GET /hosts/{id} only; the list endpoint keeps HostResponse to + # avoid per-host join overhead on bulk reads. + HostDetailResponse: + type: object + required: [host, compliance_summary] + properties: + host: {$ref: '#/components/schemas/HostResponse'} + liveness: + allOf: [{$ref: '#/components/schemas/HostLiveness'}] + nullable: true + description: Null when no liveness probe has ever run against this host. + compliance_summary: {$ref: '#/components/schemas/HostComplianceSummary'} + HostListResponse: type: object required: [hosts] diff --git a/app/internal/server/api/server.gen.go b/app/internal/server/api/server.gen.go index 8928ab9e..403f078a 100644 --- a/app/internal/server/api/server.gen.go +++ b/app/internal/server/api/server.gen.go @@ -186,6 +186,27 @@ func (e HealthResponseStatus) Valid() bool { } } +// Defines values for HostLivenessReachabilityStatus. +const ( + Reachable HostLivenessReachabilityStatus = "reachable" + Unknown HostLivenessReachabilityStatus = "unknown" + Unreachable HostLivenessReachabilityStatus = "unreachable" +) + +// Valid indicates whether the value is a known member of the HostLivenessReachabilityStatus enum. +func (e HostLivenessReachabilityStatus) Valid() bool { + switch e { + case Reachable: + return true + case Unknown: + return true + case Unreachable: + return true + default: + return false + } +} + // Defines values for LicenseStateResponseStatus. const ( Active LicenseStateResponseStatus = "active" @@ -497,6 +518,15 @@ type HealthResponse struct { // HealthResponseStatus defines model for HealthResponse.Status. type HealthResponseStatus string +// HostComplianceSummary defines model for HostComplianceSummary. +type HostComplianceSummary struct { + Error int64 `json:"error"` + Failing int64 `json:"failing"` + Passing int64 `json:"passing"` + Skipped int64 `json:"skipped"` + Total int64 `json:"total"` +} + // HostCreateRequest defines model for HostCreateRequest. type HostCreateRequest struct { Description *string `json:"description,omitempty"` @@ -510,11 +540,33 @@ type HostCreateRequest struct { Username *string `json:"username,omitempty"` } +// HostDetailResponse defines model for HostDetailResponse. +type HostDetailResponse struct { + ComplianceSummary HostComplianceSummary `json:"compliance_summary"` + Host HostResponse `json:"host"` + + // Liveness Null when no liveness probe has ever run against this host. + Liveness *HostLiveness `json:"liveness,omitempty"` +} + // HostListResponse defines model for HostListResponse. type HostListResponse struct { Hosts []HostResponse `json:"hosts"` } +// HostLiveness defines model for HostLiveness. +type HostLiveness struct { + ConsecutiveFailures *int `json:"consecutive_failures,omitempty"` + LastErrorType *string `json:"last_error_type,omitempty"` + LastProbeAt *time.Time `json:"last_probe_at,omitempty"` + LastResponseMs *int `json:"last_response_ms,omitempty"` + LastStateChangeAt *time.Time `json:"last_state_change_at,omitempty"` + ReachabilityStatus HostLivenessReachabilityStatus `json:"reachability_status"` +} + +// HostLivenessReachabilityStatus defines model for HostLiveness.ReachabilityStatus. +type HostLivenessReachabilityStatus string + // HostResponse defines model for HostResponse. type HostResponse struct { CreatedAt *time.Time `json:"created_at,omitempty"` @@ -863,7 +915,7 @@ type ServerInterface interface { // Soft-delete a host // (DELETE /api/v1/hosts/{id}) DeleteHostByID(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) - // Fetch a host + // Fetch a host with liveness + compliance enrichment // (GET /api/v1/hosts/{id}) GetHostByID(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) // Update mutable host fields @@ -1100,7 +1152,7 @@ func (_ Unimplemented) DeleteHostByID(w http.ResponseWriter, r *http.Request, id w.WriteHeader(http.StatusNotImplemented) } -// Fetch a host +// Fetch a host with liveness + compliance enrichment // (GET /api/v1/hosts/{id}) func (_ Unimplemented) GetHostByID(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) { w.WriteHeader(http.StatusNotImplemented) @@ -2538,96 +2590,100 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // const string: with thousands of chunks the chained `+` fold is several // times slower for the Go compiler than parsing a slice literal. var swaggerSpec = []string{ - "7F17bxs5kv8qRN8Ca2MkP/IY7CpYHDKeZJO9zIzhJDsHzOYEurskMe4mOyRbji4wcB/iPuF9kgMf/WA3", - "2d1yLNmzm39mHImPYtWPVcVisfQlilmWMwpUimj2JeIgckYF6H/8gJML+FSAkOpfMaMSqP4T53lKYiwJ", - "o8cfBaPqMxGvIMPqrz9wWESz6N+O66GPzbfi+AXnjL+ga0hZDtHNzc0kSkDEnORqsGgW/R2nJNEjT1CO", - "l4TavxlHJIEsZxJovEGgxoluJtFPIFcs+ZnJ52nKriHZH6W/ckaX6NW7d+co00REqo3trkZ/nmSEXrAU", - "xIXlqvo05ywHLolhMVdfqz+IhEwM0aQGe0El36iVy00O0SzCnOONnprDp4JwxYLf7Lgfqlbs8iPEUnV7", - "XiREvlhb/rjU4Nis7UvZTUhO6FJ1w7FkfE40f2mRpvgyhWgmeQGTUGPzsWesmHEOqRaKHbHTJAGJSapp", - "ShKiWuL0vEGrM3G9ODPagvEMy2gWFQVJIg99LI4LziGZY+m0T7CEqSQZ+DpxiBlPtu6UGKa6Qu60c4Wp", - "+glW8BjGcrxqXzJ9sIeANXAiNx5yWljSPGzJbFJixRG2y9l+9IlzvPRsiIpFozZEA8weJlL4LOdxwQXj", - "aiB3+/6S408FIPP1M5RjIRAW6N/NB39BkqEFyHiF5AqQGkmpI7XEAc62maeX4dLiZ4xcvWFLQhsa1+UM", - "k7n6X4Y/vwG6lKto9v0kyght/KsjZLWqa8aTVsdHT92up56uhQBOcQZbd20xoBqnQc0AA0LKEscxCDGX", - "7Ar8SorDgoNY9bRQ1AyDSq5+goqM9oIcKtpz2hlCC/zp5fMXlLM0DS8y52xNBGGU0OW84GR4f3Z69Mz+", - "d+BksblDjLVoUQMEp4dz4BkRitQek0gSoNIqpvY3XpkSMceU0U3GiqZyvWQsBUw1LlgKI/Wcbtoa07eg", - "vF7KNpq9M6VdqztgmINhtkFmDWaXP+OMYoBJriYYwcHGhjc02aFDizq3WuFshekSgtDUdoXKuaPS+lUY", - "hevxzVtL6UzXGi60mgujDoLLGFJRbT/Oae6b9AxLWDK+MX5hZz7H6AXBMUKszYG8dHDQYMbpGQcsw4LE", - "hVzNrcescEuLTE0ixGp+BZummZhEl0yuGrM1/UNnWQ2FdXry6MnEqyQSWOAilX4VcStT5xrY7pecrLEE", - "vayB7zWw8hXHwr8HRcyMX1exayMkZNEkWjEhvSzSXeb+zT/oHd6V9Td0W/Y6uqEJg348vSFChvVeXLUb", - "7zbWY9dWfkBTN6fpJ7fHf7kL5Md6d213Cin7XG5GWYJxOmNwGGVDY0nW4N9xY3fkve8FK5/5JZFNE0+o", - "hCXwZouYZZk9WAdHWRC6BJ5zMtAueHou8mRrAGxpwsftWkeETXF790chJMsuWAoD5iGs2cfoYyPiHEsJ", - "XJ3y/us3PP3vD+o/J9M/zz98OZ18//jmDz4WjXboMkJfmy9PB727lt0c9vJqNoXVyK1MupbPZUFSOSfU", - "v+HuyqXtLLo58zALXsQrFkRHBkLYuEHH5m9jl8pxwgSElXhC5BzWyjkcqQhHxLsgXjEY4YrZdp0xveug", - "nwoo4B0I+Td22WNBh8n7yC7HLbZFru03jlwn4No93+iIr4f4ZN9BxspmlfYnTonS+pNIAF8DVxhnKYmV", - "RYfPShHh1GuVVkWG6bwBaU9AQ/KNMU7dHdv2UBQnSuqaXdsTdZnfBplmtVdGa5wWWMLzFLgMblIRM15u", - "UZIpHp2enOj9af51MunY0K7byP2b82UKIF8xIV9ikhbcA5QFJimhyzkvUpjHrKCunSRUfv8kmnisuPIe", - "boXysuPEN3dwEW/IGigI0V0BhTXwec7ZJSQjaeeA41UJlBHtC3pF2TUd3Xq78TuH2LKzO1RNxsRdc5Bn", - "FxADlSZSIL42eKxHfMcxFTaWPWja9Khh4ooUBlGpsbINKjWUxpzSy4YT31xBot+We7UVhcRCqCEWvL4T", - "cuPn56YFUtMKdIwObBf0HbLzHyIccyYEwmmKFC3iCJ0cHZ0eKRIrt5UVBgqWPFpkl2bhkkmczsFonNIp", - "cWk4U2tDbKFHN5tOSCwBcXYt0PUKuA7w60CO+qIQiAgT6mdck+nQMhLOHd74aA0y/B3LXxr+KC12NyBu", - "6sOvBHFNnoLz3ZDX3BhfS15ju3Z9Aa0X5leEOufrBeFCzgXo8LzGx9y01Ecdew1WfeQ11KMtw46vIIO6", - "oPc+z6zaBMdLpigQW1WhuHBF8lzzo237+46Jtd2rdY+daeIIY/he8BXgVK56zjyX85hRCrF0vOTG6aW7", - "xJUec6OPI0uOk4Bw18CF/yjVdkrKpTnE1AN418WEvPWBNxDKTIjIU7yZh+Jz3cMFXRPOaBmaaN7u+MZf", - "clbktw2WKEzcMpZK8jlOEm5dohaVQ3FYxqXjcX7/9Onjpw2f89RnWyVeuqpsiDXtG+a+MOmwz2hjKo11", - "hyDUHwJdlYZklEpW440Oe5qhQ2T1RmXvO0rZ3iZD22J3++DWAVRnQwQxPwLXw0DefVDRj3i7DFcaIby9", - "11Q+WE06oKO+Ck//9MqxI/A3JAYq4K3UIg8qGh0wBR4MrH3OCQfRB+xB3i8Ay4LDlluK0PmS4xjmOXDC", - "RnstNoau4IJjfYmuV6BBwuapYYraQ3SNU+J3aSQxWS6VB8xBJ2flQK+xjFfzPNWODFCpryIEeIcp9DEn", - "57BuXWGGQlB63oYPWDHuQ1i+AxkpdsHzj9dyWM80G4+YMphKsTfMBIapMSTmRspe8JRiHiRmrRc85yDc", - "27a6xTXmlNDlV5HbVvol7e35fZKpM4MCeQyxTXPwm3p11ODB9J9bXpiUaFpiCWOzJCoy23cgNYn9yxcX", - "sCRC8h542jnIFonDbpKIR1mF7n76Bm3LzJfGuqP05gYLXNonPZnP5ywlMQFxASnDSZi/rJAxy+xtj/di", - "IKz9AxH1asggXZsfIdZJfH33fre6rhi+ZbDUBZxMRVz4Ntp+Hz5E69i0GHO+LslwJ+1MUQ3o42UNod//", - "tamL6yYdvpW/F8AHIg1VimDPwfzx7bOI/7SfLOIyqbA3FU9x425Pp1+dX5liUacT2nDjduet3Z/ROmwO", - "8pal8FwIsgznqytFbL3ybSRddgvNLPpjIWoF4y2OA5OhLWqG9tyeamd/wbr3E28lXgI6Qdc4vSJ0ORVX", - "kIJkFImCL3AM6P/+53+RXHEABDTJGaFSILnCEsFn4DERgOQK/kEXrKDmURQSEsdX6KBxuz1pPoyaIJ2f", - "MDEPpBDY6/TDo3/oawoilfMW/ZID/VUdBNCBJfGwEcecRSdHp0cnU5zmK3x0qi1EDhTnJJpFj49Ojh7r", - "3SdXmr3HOCfH69NjnGSEHlufaWY8Pi0eZsChhKTpfZ1Es+icCanfRzkueWQYDkL+wJLNnT3l8p40blzx", - "KuNp3tHU798enZzsiobqbUH3AZxqYedAxmNGB6LQjw4Q40iBL9F3VwWHQ/PmrMgyrExe9CPfTHlB0dq8", - "ogOE0d9+fYesVNA1kStWSESokDhNCV0iYpwVV4q59ZRmXLtKI8To+lbRDhkZ8OI8nDwHPlXcQmYVqHLC", - "bibRk5PH+3speIbTFDhKcXwlkPFmSs664jNrQkqtQmJbogVJQaAFZ5l+ihQzuiDLgkOCEsIhlua08Xla", - "Ynla+w3RLOpOV4laKYpjncikhbQEj4D/CrLxZEvve44zkFrJ/vYlUk5R9KkATYMxNPXLsJp9HZXv79l5", - "ZLb1CM5jtK17C0Jjt+MYKxsaraDSXK7dyWj27dgtVpWSjEinY5V3+/RkUkcFHzlJOp6Y4M2HHe7r9stA", - "34ZWpootjIlDFrl6M5+ERq/IPW48ZnY3nfInnCHRgeH11L4/hmSCKFyDkEhfJR+2tpFcHadsaY4HPWqy", - "fN+2IyPXeUC4ZwPXfb/nkaBugLQ1gwSSZ0i/axGICFFAYmR5uj/F/NrEUFEjuV+Z2J9ePkcV4zRYIC7M", - "3fpvH5rQeW/d5ePSrUcaCNrOIpabIzp698u7cy9kWCFHYUa160juicfdBK31EYc1u4KucVGfGhtijb8w", - "HbrEmQND2CLI1U8Q7RhMzhPMrujKl3P7xszPzHhXJfMUYC4Bc5so1OS3LDh1+F099/Mw/LgVR+hn/rkb", - "89qtHHzPNv3+lm2GUtJRsy8WC9BXG6ixULRQHu04Di3wDPTb2eE9Uz2z3TlvOu95fXxpvM9F7y9eI66R", - "obx4u3q1QLXuGEvGEc7zNu/0HA6j1GEUEaqVi1JYfoaNOoY1Xwbv0Dh1Xh+PMlAeNafUs14YuQeToSZX", - "LLfXb2r3c8hTvOno2zPlqPMMYYoMbiHRlkVAzEGiy41ZxEZJE6MlUCUYSJDXWpQGZmbiRsMSdV/V7lCs", - "/ue7t5VtORqywa6Ge7cn9Q7XqLLmHD7qJC8lLJvSv2+8nZn01Zqma85s1KwBNc37jn74o6i6eRBVa+EZ", - "t3dOQ3bHc02106N+z62Yh1UVSS5zXhZpii5+eH6GymWig/r+aNI0RxOkQ+xTQpG+RvJ4+vZB9vAGtA/B", - "d7jzWk/NH6K/r3aTdvFRjgm/LzffMspSYjX3BNnEionR4YWARAFDxDgB60IjyclyCRySw95zwAXTWedq", - "//HmXM9QRqhEWB0fkakb8l3ZQDHEgVfrIXNoH541mu1QvIHn1z4NVbVEGUicYImt/3efYbaamzMOnUCb", - "PvM3T30HFemMppvDnoBaZ+BJjyJoC+vuFUGo7MIobXC6AzJGQsVewe3dvjfry5U+nH7uPNGPSBBlEunr", - "FlNyzrZ4+/YVugJr/f+8R2+zSCXJU0DmiTuygTvRtv+amQg3ID0OwdecSAhpoeMvJLkxLloKEroI/1F/", - "Xgv1h83rHwMh4hzLVR2RNG8GHHB6w6SB13cfxniShrgEHVTv0v+ywKmAQyPEJ3vUSzXqK3S1BPiWLeTU", - "sPkWUrTyuZmMMBr3J6OT+1M2pXJ/iKJ/qYvMNYX+R1ETvIUdamzihOAlZUKSWMwgXrF+b/XHuvUL1diP", - "jhXgRGd3Wny8ri+dp/8Bm16wOJkfTwczPwIz/uf0rL4fmr7uvx/6sBtz2yxOsGeH2ylL4MGa+r4Ord/q", - "XkR1eTrcpVNzdd9msYW90nvXYfeELBagj8yXSvCtQJrikXHT9ZKRTYt7ViVcCKfM7HfmYii8uUyJhakE", - "Iacf2eX4jebUZug68o/ujpn+KhAerv6NXepDSi4heYauGb8Cjq5JmqKEY0LbJkviJUxPkB4dJZCxZ8iy", - "Q6gTD5uyHH1klyjnTB18TBRF8Z7Qqf3MThJmry03MMUpcDmeuc0qBTvyu72VEPasEQKJo74QtEklSGzT", - "gCjzspWWpV2gMBFqtcy5XKmjK0sTUYXDApLLOWSkyKZbWZ9z0+lBGKF/TftR6vJH+9PlNjEKJQyEdpII", - "jdMiAWQhNG/ACtlXFZ3TvB5iqhP1kYLcs/L+VvQO43hX5YezyNMlCHTbfaoOj1PtiI1F+4Xpad5R6pyp", - "b4i/D8Sjg9gEjoxiU4LUHvXh/QawKjoCylpHtI2qrrBe9WkEtXuOEO4Ug+g2wYJbwPtX3fEbvh8OvrUo", - "HwLA6wDUFgjXnbaAeDfK5cM4hwwSYg6X8BniYnuwX9RDvLAjfEP9v7of08DV3ODK1pq6v63XIGlWQj28", - "B78r08dbe9EzCvrOv9whj8vPo/C2Di2g3N+LFEAep40Ka6HIqFuKbYewdSfyAsh8hzhL0yLf+y3p8zoJ", - "ySQRlgmQ94lUc+/R8WlcsCofA+k6ZwJdbpAtMEdSIjfIvkXvgoPrUnLTuK4l1wsRt/JcR6u7S3hJUgkc", - "SYZkXa9KIKWUY5ludDYxR3KFKZIrItDFy7PHjx//GUmSgZA4y/X7/l3np4/NCz89udfEcA/7vXf8qkGT", - "3+b3Wm4bBv228UZsvC7TBTowFfjsvjp0U+c9+7AqFtq7/UyZwl2jzMziYY3+FhlSv8FjLDw016bXJAGk", - "JkwJpjEYLlblIo916cRDDzAky6e2kuS0qnDVC5J2acVRr5V+T1qwvUCfHsT0ChKTy2AfCYpvSnDX3ocw", - "d09yBSgrOV8XRz1QUwBNCF0OAZ2XRTdHAt0U6fwnBrpZYBjoimHfgL4fa6/B7Ae6VtBhoJtSnH24NgVA", - "d2nhWyVGPSx5C3xNYkBEoLJ26M0kerpPqTRIKIuWIsZRQfEaE1N7qS8RtTpDqnM4Toj+WxfVRgfV75m1", - "RDNkWrewpc3yeLd4qCrxcjinY1foaFfZ9AhHnzPvP7U1dDOgk1pXVlojYv59iaul0HcRO+zWxN1zsqpb", - "ATUg5/tPUC3NGjq4xMajU/tkgkg+QTnjcoJAxkeHe8++eWUpQThVQNog+EyU9icUNRVAIEVV/0bT1gF7", - "DevjL7bi9E0zTXXGQbB0PRCsf+VmY1/YPmMyIRtlrn/H6ZB2xc3HzveXF/kza5JR2Tb9IFIfXRhvpTx3", - "Ig96NebJUz2Srp2jU5YuAemcMDXiIOj6cikt8kalQiuQPcwkaEN3MscSCZD7z4F+5eTW92Y/j1EQwxnP", - "9yuKk73aqoclzTKheYwcK0cEy3jlUdzq471Lcjcej1u7es93pqM8nvt6ctv1eB4WoI3kUFZIbaNMaI1A", - "mojt/ZiyfnPPeetNVeJ518XI3NLaPXfaOqTf4spfQRrrax8op8226EAS4JPy2ldM0KeCSSwm9j7OPYJW", - "VWJDLLlgJsq1u8esSUaonqXXhWIpPIDzn2JX8PznPmDuu0J3RnFEMTNnn36HWnPLePW7etoY+MnQfT9t", - "7P4kp/edvmqluX5vR8dGAZbyh9X2fTbUW4QkSOIroMFXijWvhgDa1aBVgc+QstBFQnepLLpVSD2MUI0e", - "gK5Q3ArqisJyKiSBRue+WFHN8LvXAd2qxnve/W6F2ICcH0CsiPGqXmTTh7pX3PmSOysloFoMYS+w+0ee", - "xpVoHuJpfO/ercbouMP3GKkMH77vl/Mne938D0ua5eF7jBw7zl+9uawfiHWF734/sKwGLkw58N/xKd1f", - "1/y2lay0J2QYeA+W4b3x/4w/StooMStE2Hwt2TBgtDNm4dALmYJuBZr3ZfNvsGnAhkPG1rp2RfkuQB52", - "AuCqSSlCXR16SyHelL9X7stgfcNinKIENMLsfW7B02gWraTMxez4OFUtdJTjT0+ePI5uPtz8fwAAAP//", + "7F17bxs5kv8qRN8CayOSH3kMdhQsDhkn2WQuM2M4yc4B2ZxAd5fUjLvJDsmWowsM3Ie4T3if5MBHv8nu", + "lmNJnt38k9gSH8WqH6uKxWL5axCyNGMUqBTB7GvAQWSMCtC//ISjC/icg5Dqt5BRCVT/iLMsISGWhNHj", + "T4JR9ZkIY0ix+ulPHBbBLPi342roY/OtOH7BOeMv6AoSlkFwc3MzCSIQISeZGiyYBX/HCYn0yBOU4SWh", + "9mfGEYkgzZgEGq4RqHGCm0nwC8iYRb8y+SxJ2DVEu6P0d87oEr169+4cpZqIQLWx3dXoz6KU0AuWgLiw", + "XFWfZpxlwCUxLObqa/UDkZCKIZrUYC+o5Gu1crnOIJgFmHO81lNz+JwTrljwwY77sWzFLj9BKFW3Z3lE", + "5IuV5U+TGhyatX0tugnJCV2qbjiUjM+J5i/NkwRfJhDMJM9h4mtsPnaMFTLOIdFCsSN2mkQgMUk0TVFE", + "VEucnNdobUxcLc6MtmA8xTKYBXlOosBBHwvDnHOI5lg22kdYwlSSFFydOISMRxt3igxTm0LutGsKU/UT", + "LOchjOV42b5g+mAPASvgRK4d5LSwpHnYktmkwEpD2E3O9qNPnOOlY0OULBq1IWpgdjCRwhc5D3MuGFcD", + "Nbfvbxn+nAMyXz9FGRYCYYH+3XzwVyQZWoAMYyRjQGokpY7UEgc422aeXkaTFjdjZPyGLQmtadwmZ5jM", + "1H8p/vIG6FLGweyHSZASWvutI2S1qmvGo1bHh0+aXU8dXXMBnOIUNu7aYkA5To2aAQb4lCUOQxBiLtkV", + "uJUUhwUHEfe0UNQMg0rGv0BJRntBDSrac9oZfAv85eWzF5SzJPEvMuNsRQRhlNDlPOdkeH92evTM/nfg", + "ZLG+Q4y1aFEDeKeHc+ApEYrUHpNIIqDSKqb2N06ZEjHHlNF1yvK6cr1kLAFMNS5YAiP1nG7aGtO1oKxa", + "yiaavTOlXWtzQD8H/WyD1BrMLn/GGUUPk5qaYAQHaxve0GSH9i3q3GqFsxjTJXihqe0KlfOGSutXYRSu", + "xzdvLaUzXWs432oujDrwLmNIRbX9uEZz16RnWMKS8bXxCzvzNYyeFxwjxFofyEkHBw1mnJxxwNIvSJzL", + "eG49ZoVbmqdqEiHi+RWs62ZiElwyGddmq/uHjWXVFNbpycPHE6eSiGCB80S6VcStTF3TwHa/5GSFJehl", + "DXyvgZXFHAv3HhQhM35dya61kJAGkyBmQjpZpLvM3Zt/0Du8K+tv6LbsbeiGOgz68fSGCOnXe2HZbrzb", + "WI1dWfkBTV2fpp/cHv/lLpAf6t212Smk6HO5HmUJxumMwWGUDQ0lWYF7x43dkXvfC1Y+80si6yaeUAlL", + "4PUWIUtTe7D2jrIgdAk842Sgnff0nGfRxgDY0ISP27UNEdbF7dwfuZAsvWAJDJgHv2Yfo4+NiDMsJXB1", + "yvuvD3j63x/VPyfTH+cfv55Ofnh08ycXi0Y7dCmhr82Xp4PeXctuDnt5FZv8auRWJl3L5zIniZwT6t5w", + "d+XSdhZdn3mYBS/CmHnRkYIQNm7Qsfmb2KViHD8BfiUeETmHlXIORyrCEfEuCGMGI1wx264zpnMd9HMO", + "ObwDIX9mlz0WdJi8T+xy3GJb5Np+48htBFy75xsd8XUQH+06yFjarML+hAlRWn8SCOAr4ArjLCGhsujw", + "RSkinDitUpynmM5rkHYENCRfG+PU3bFtD0VxoqCu3rU9UZf5bZBpVjtltMJJjiU8S4BL7yYVIePFFiWp", + "4tHpyYnen+a3k0nHhnbdRu7enC8TAPmKCfkSkyTnDqAsMEkIXc55nsA8ZDlt2klC5Q+Pg4nDiivv4VYo", + "LzpOXHN7F/GGrICCEN0VUFgBn2ecXUI0knYOOIwLoIxon9Mryq7p6Nabjd85xBadm0NVZEyaa/by7AJC", + "oNJECsS3Bo/1iO84psLGsgdNmx7VT1yewCAqNVY2QaWG0phTetFw4prLS/TbYq+2opBYCDXEgld3Qs34", + "+blpgdS0Ah2jA9sFPUB2/kOEQ86EQDhJkKJFHKGTo6PTI0Vi6bay3EDBkkfz9NIsXDKJkzkYjVM4JU0a", + "ztTaEFvo0c2mExJLQJxdC3QdA9cBfh3IUV/kAhFhQv2MazIbtIyEc4c3Llq9DH/HspeGP0qL3Q2I6/rw", + "G0FckafgfDfk1TfGt5JX265dX0DrhfkVoY3z9YJwIecCdHhe42NuWuqjjr0GKz9yGurRlmHLV5BeXdB7", + "n2dWbYLjBVMUiK2qUFy4Ilmm+dG2/X3HxMruVbrHzjRpCGP4XvAV4ETGPWeey3nIKIVQNrzk2umlu8RY", + "j7nWx5Elx5FHuCvgwn2UajslxdIaxFQDONfFhDxjaZYQTEN4m6cpdkVpS+d2hEGw2nVka6usRrYucDCu", + "tdZ6t3EICqKqxXQhWAzv5eptwwieAHFERJbg9dwX9ewe2eiKcEaLgE/9zsw1/pKzPLttCErttFtGqEk2", + "x1HEraPZonIous24bPjxPzx58uhJzZM/dcICL5sGYog17Xv7vuDzsCduI1W1dfsg9Fyf//oOxsXGnYtq", + "5/aZO/d2twIc07kej05qBwScJL8tgtmH4RHKY8XNx3bG0q95kiiviCLKUDE60j43irFAyoIgnlOEl5hQ", + "IZGMidC+1VEHpC7O63N+h2U+7veH9ePCORrlZrRZ1+timKH9ZPmOZaEaPcwlWcF8YdwZTzw4wULOtRob", + "n4+j+2hZ9LkE44YpsvjmhnOeTm2C667RN9FgT3gkUX5V1y4PHwcH3Q/XDD6B9l4d7fsqpW11hqzM9szK", + "rW95GvbFa0JGmIlhu7D9mw+3AbHLaErDh7f3msp765gMmPxvwtM/va/REfgbEgIV8FZqkXsVjb7VAe6N", + "/n/JCAfxTUp3AVgWFmn8liJ0vuQ4hHkGnLDRRyt70afggkOd6aNXoEHC5olhitpDdIUT4j53SWJS8cpj", + "OgedQZoBvcYyjOdZok9bQKW+LxXgHCbXsZiMw6qVZ+GLk+t5awfVknEf/fIdSJuzC55/upbDeqbeeMSU", + "3nyvnWHGM0yFITE3UnaCpxDzIDErvWDluzRTAqoW15hTQpffRG5b6Re0t+d3SaZKX/QkW4U2F8tt6pVf", + "xb05ire81S3QtMQSxqZylWS2L2orEvuXLy5gSYTkPfC0c5ANXjc0M9kcysp3Qd03aFtmrlz7Lb3BqLGg", + "Sfuk53nGOUtISEBcQMJw5Ocvy2XIUnsl7by99Gt/z7VfOaSXrvVzCHWmcV9ywq3uVIevQi11HidTEedP", + "mbHf+yN9+swixgQBCzKak3amKAd08bKC0B8/t6OJ6zodrpW/F8AHAndlHnNPnOvR7Z86/GU3Tx2KzOfe", + "fGHFjbs9nX5zEriJQliiR8QC9pGd1mGzl7csgWdCkKX/UY1SxNYr30TSRTffzKI/uKVWMN7iNGAytEXN", + "0I4UD+3sL1j3EvWtxEtAJ+gaJ1eELqfiChKQjCKR8wUOAf3f//wvkjEHQECjjBEqBZIxlgi+AA+JACRj", + "+AddsJyal5tISBxeoYNaCs6k/npzgnQS1cS84kRgc34Oj/6h71KJVM5b8FsG9Hd1EEAHlsTD2mXLLDg5", + "Oj06meIki/HRqbYQGVCckWAWPDo6OXqkd5+MNXuPcUaOV6fHOEoJPbY+08x4fFo8NjyrhKTpfR0Fs+Cc", + "CakfcTZc8sAwHIT8iUXrO3tv6jxp3DTFqwOvk+Yj3YcnJ9uioXwA1X2lq1rYOZDxmNGByPXLKMQ4UuCL", + "kA2SHpqHsUUEPXjO11OeU7QyT30BYfTz7++QlQq6JjJmuUSEComThNAlIsZZaUoxs57SjGtXaYQYm75V", + "sEVGerw4ByfPgU8Vt5BZBSqdsJtJ8Pjk0e6eM5/hJAGOEhxeCWS8mYKzTfGZNSGlViGyLdGCJCDQgrNU", + "v5cMGV2QZc4hQhHhEEpz2vgyLbA8rfyGYBZ0pytFrRTFsc621EJagkPAfwNZe1eq9z3HKUitZD98DZRT", + "FHzOQdNgDE31fLViX0flu3t2XsJuPELjxezGvQWhYbPjGCvrGy2n0mQA3Mlo9oHrLVaVkJTIRsfyccCT", + "k0kVFXzYyCR0xARvPm5xX7efL7s2tDJVbGFMHLLI1Zv5xDd6Se5xreJCc9Mpf6IxJDowvJ7aIgkQTRCF", + "axAS6XyXw9Y2kvFxwpbmeNCjJotHuFsycp1Xzjs2cN1Hxg4J6gZIWzOIIHqK9OM7gYgQOURGlqe7U8yv", + "TQwV1V4gKRP7y8tnqGScBguEuUkA+vCxDp331l0+Ltx6pIGg7SximTmio3e/vTt3QoblchRmVLuO5B47", + "3E3QWh9xWLEr6BoX9amxIdb4C9OhS5w5MPgtgox/gWDLYGq8E++Krnjeu2vM/MqMd1UwTwHmEjC3uTh1", + "fsuc0wa/yzfJDoYft+II/cw/b8a8tisH19tyt79lm6GEdNTsi8UC9NUGqi0ULZRHO45DCzwD/cB/eM+U", + "tQC2zptO0QEXX2pFBND7i9eIa2QoL96uXi1QrTvEknGEs6zNOz1Hg1HqMIoI1cpFKSw3w0Ydw+rlC7Zo", + "nDolEkYZKIeaU+pZL4zswWSoyRXL7fWb2v0csgSvO/r2TDnqPEWYIoNbiLRlERBykOhybRaxVtLEaAlU", + "CQYi5LQWhYGZmbjRsESbT/+3KFZ3jYHbyrYYDdlgV82925F6h2tUWnMOn3QmqhKWfXe0a7ydmRz7iqZr", + "zmzUrAY1zfuOfvizKLs5EFVp4Rm3d05DdsdxTbXVo37PrZiDVSVJTea8zJMEXfz07AwVy0QH1f3RpG6O", + "JkiH2KeEIn2N5PD0bdWI4Q1oq1Vscee16mHcR39f7Sbt4qMME74vN98yylJiNfcE2cSKidHhuYBIAUOE", + "OALrQiPJyXIJHKLD3nPABdNPY9T+4/W5nqKUUImwOj4iU9zoQdFAMaQBr1a1Bd8+PKs126J4PTUiXBqq", + "bIlSkDjCElv/b59htoqbMw6dQJs+89dPfQcl6Ywm68OegFpn4EmPImgL6+4Vga82zChtcLoFMkZCxV7B", + "7dy+14tgFj6crskw0dnYiDKJ9HWLqYtpW7x9+wpdgbX+P+7Q28wTSbIEkKnDgWzgTrTtv2YmwjVIj0Pw", + "NScSfFro+CuJboyLloCELsKf688rof60fv3cEyLOsIyriKR52NQApzNM6nki/HGMJ2mIi9BBWTzjrwuc", + "CDg0Qny8Q71Uob5EV0uAb9lCTg2bbyFFK5+byQijsT8ZnexP2RTK/T6K/qWuhFkX+p9FRfAGdqi2iSOC", + "l5QJSUIxgzBm/d7q86r1C9XYjY4YcKSzOy0+XleXztP/gHUvWBqZH08GMz88M/7n9Ky6H5q+7r8f+rgd", + "c1uvoLJjh7tRO8WBNfV9FVq/1b2I6vJkuEunMPSuzWILe4X3rsPuEVksQB+ZL5XgW4E0xSPjpuslI5sW", + "97RMuBCNWtgPzMWQf3OZOjBTCUJOP7HL8RutUUCm68g/vDtmukvVOLj6M7vUh5RMQvQUXTN+BRxdkyRB", + "EceEtk2WxEuYniA9OoogZU+RZYdQJx42ZRn6xC5Rxpk6+JgoiuI9oVP7mZ3Ez15bE2WKE+ByPHPrpVS2", + "5Hc7y7XsWCN4EkddIWiTShDZph5RZkUrLUu7QGEi1GqZcxmroytLIlGGwzySyzikJE+nG1mfc9PpXhih", + "f037Uejyh7vT5TYxCkUMhHaSCA2TPAJkITSvwQrZVxWd07weYqoT9ZGC3NPi/lb0DtPwrooPZ4Gjixfo", + "tvtUHR6n2hEbi/YL09O8o9Q5U98Rvw/Eo4PQBI6MYlOC1B714X4DWCUdHmWtI9pGVZdYL/vUgto9R4jm", + "FIPoNsGCW8D7d93xO77vD761KO8DwKsA1AYI1502gHg3yuXCOIcUImIOl/AFwnxzsF9UQ7ywI3xH/b+6", + "H1PD1dzgyhbE29/Wq5E0K6Du34MPivTx1l50jIIeuJc75HG5eeTf1r4FFPt7kQDI43qVF19ktFkvcouw", + "bU7kBJCtGsNZkuTZzm9Jn1VJSCaJsEiA3CdSzb1Hx6dpglX5GEgXYxToco3qFUyQfYveBQfX9S6nYVXw", + "shcizfKYHa3eXMJLkkjgSDIkq6J6AimlHMpkrbOJOZIxpqYO0MXLs0ePHv2IJElBSJxm+n3/tvPTx+aF", + "n57sNTHcwX7nHb9qUOe3+aNStw2Dft94IzZel+kCHZgyoXZfHTZT5x37sKxo3Lv9TC3VbaPMzOJgjf4W", + "GVK/w2MsPDTXptckAlQVLzNcLGvaHutShIcOYEiWTW0Nw2lZsqwXJO36r6NeK/2RtGB7gS49iOkVRCaX", + "oayk9l0Jbtn7EObuScaA0oLzVQXnAzUF0IjQ5RDQeVEZeCTQTSXhf2KgmwX6ga4Y9h3ou7H2GsxuoGsF", + "7Qe6qRfch2tTpXibFr5VB9nBkrfAVyQERAQqChzfTIInu5RKjYSisjJiHOUUrzAxtZf6ElHLM6Q6h+OI", + "1KqQHpR/dLElmiHTuoEtrZfHu8VDVYmXwzkd20JHu2yqQzj6nLn/1FbfzYBOao2ttEbE/PsSVwuhbyN2", + "2C0xveNk1WZJW4+c95+gWpg1dHCJjUen9skEkWyCMsblBIEMjw53nn3zylKCcKKAtEbwhSjtTyiqKwBP", + "iqqtZrxhwF7D+virLYt/U09TnXEQLFkNBOtfNbOxL2yfMZmQtVr8f+B0SLvi+mPn/eVF/srqZJS2TT+I", + "1EcXxlspz53Ig16NefJUjaRr5+iUpUtAOidMjTgIur5cSou8UanQCmT3Mwna0B3NsUQC5O5zoF81cut7", + "s5/HKIjhjOf9iuJu/ZJWMX0fe019QHRgL55EVYf+AeoWj79nGCjSoPXu12cMJ/UIKCdhbD3McU4OlmHs", + "MArq452jZDveVLMu9o7vY0d5U/t6ztv1pu4X7I3kUJpLbf9M2I5AEonNfaSiNnTPWe5NWT5624XOmmW7", + "e+7L9XVBiyt/A2ksu338nNTbogNJgE+KK2UxQZ9zJrGY2Lu+5vG2rEDrY8kFMxG07T2UjVJC9Sy97hlL", + "4B6cLRW7vGfL5uPovuv5xigNUczMuarfWdfcMieGbT2b9PzN5F0/m+z+TWJnDQDVSnN9b8fSWnGX4k+J", + "7PrcqbcIiZDEV0C9LyArXg0BtKtBy+KhPmWhC5BuU1l0K5w6GKEa3QNdobjl1RW55ZRPArXOfXGoiuF3", + "rwO6FZN3vPub1Wc9cr4HcSjGy1qUdR9qr7hzJY6WSkC1GMKeZ/ePPOkr0dzHk/7OvVuN0XEH+zFSGT7Y", + "75fzJzvd/PdLmsURfYwcO85ftbmsH4h19fB+P7CoNC5MqfE/8CndXTP9tlWytCdkGLgHy/De+H/GHyVt", + "lJgVImy+lmwYMNoZs3DohUxONwLN+6L5d9jUYMMhZStdF6N4cyAPO8F11aQQoa48vaEQ9RU1X7mzY9+w", + "ECcoAo0wG8nLeRLMgljKTMyOjxPVQkc5/vL48aPg5uPN/wcAAP//", } // decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, diff --git a/app/internal/server/api_hosts_enrichment_test.go b/app/internal/server/api_hosts_enrichment_test.go new file mode 100644 index 00000000..1338980b --- /dev/null +++ b/app/internal/server/api_hosts_enrichment_test.go @@ -0,0 +1,189 @@ +// @spec api-hosts +// +// AC traceability (this file): +// AC-13 TestHosts_GetByID_Enrichment_LivenessPresent +// AC-14 TestHosts_GetByID_Enrichment_LivenessNullWhenUnprobed +// AC-15 TestHosts_GetByID_Enrichment_ComplianceSummaryCounts +// AC-16 TestHosts_GetByID_Enrichment_ComplianceSummaryZerosOnEmpty + +package server + +import ( + "context" + "encoding/json" + "net/http" + "testing" + "time" + + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgxpool" + + "github.com/Hanalyx/openwatch/internal/auth" +) + +// seedLivenessForHost inserts a host_liveness row for the host the +// preceding createHostAPI call returned. +func seedLivenessForHost(t *testing.T, pool *pgxpool.Pool, hostID uuid.UUID, status string, lastProbeAt time.Time, consecutiveFails int) { + t.Helper() + _, err := pool.Exec(context.Background(), ` + INSERT INTO host_liveness + (host_id, reachability_status, last_probe_at, + consecutive_failures, last_state_change_at, updated_at) + VALUES ($1, $2, $3, $4, $3, $3)`, + hostID, status, lastProbeAt, consecutiveFails, + ) + if err != nil { + t.Fatalf("seed host_liveness: %v", err) + } +} + +// seedRuleStateForHost inserts a single host_rule_state row. +func seedRuleStateForHost(t *testing.T, pool *pgxpool.Pool, hostID uuid.UUID, ruleID, status string) { + t.Helper() + now := time.Now().UTC() + scanID, _ := uuid.NewV7() + _, err := pool.Exec(context.Background(), ` + INSERT INTO host_rule_state + (host_id, rule_id, current_status, severity, + last_checked_at, check_count, last_scan_id, evidence, + framework_refs, first_seen_at, last_changed_at) + VALUES ($1, $2, $3, 'medium', $4, 1, $5, '{}'::jsonb, '{}'::jsonb, $4, $4)`, + hostID, ruleID, status, now, scanID, + ) + if err != nil { + t.Fatalf("seed host_rule_state: %v", err) + } +} + +// hostDetailResponse mirrors the shape returned by GET /hosts/{id} +// after the v1.1.0 enrichment. The decoded body uses map[string]any +// for flexibility — strongly-typed unmarshal would require an oapi- +// generated client which lives outside this test surface. +type hostDetailResponse struct { + Host map[string]any `json:"host"` + Liveness *struct { + ReachabilityStatus string `json:"reachability_status"` + LastProbeAt *time.Time `json:"last_probe_at"` + LastResponseMs *int `json:"last_response_ms"` + ConsecutiveFailures *int `json:"consecutive_failures"` + } `json:"liveness"` + ComplianceSummary struct { + Passing int64 `json:"passing"` + Failing int64 `json:"failing"` + Skipped int64 `json:"skipped"` + Error int64 `json:"error"` + Total int64 `json:"total"` + } `json:"compliance_summary"` +} + +func getHostDetail(t *testing.T, url, hostID string) hostDetailResponse { + t.Helper() + req := asRole(t, "GET", url+"/api/v1/hosts/"+hostID, auth.RoleAdmin, nil) + resp := doReq(t, req) + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + t.Fatalf("status = %d, want 200", resp.StatusCode) + } + var got hostDetailResponse + if err := json.NewDecoder(resp.Body).Decode(&got); err != nil { + t.Fatalf("decode: %v", err) + } + return got +} + +// @ac AC-13 +// AC-13: host with a host_liveness row → liveness populated. +func TestHosts_GetByID_Enrichment_LivenessPresent(t *testing.T) { + t.Run("api-hosts/AC-13", func(t *testing.T) { + url, pool := freshAPIServer(t) + created := createHostAPI(t, url, "live-host", "production") + idStr := created["id"].(string) + hostID, _ := uuid.Parse(idStr) + probedAt := time.Now().UTC().Truncate(time.Second) + seedLivenessForHost(t, pool, hostID, "reachable", probedAt, 0) + + got := getHostDetail(t, url, idStr) + if got.Liveness == nil { + t.Fatal("liveness is null; want populated") + } + if got.Liveness.ReachabilityStatus != "reachable" { + t.Errorf("reachability_status = %q, want reachable", got.Liveness.ReachabilityStatus) + } + if got.Liveness.LastProbeAt == nil { + t.Error("last_probe_at is nil; want populated") + } + }) +} + +// @ac AC-14 +// AC-14: host with no host_liveness row → liveness=null, not an error. +func TestHosts_GetByID_Enrichment_LivenessNullWhenUnprobed(t *testing.T) { + t.Run("api-hosts/AC-14", func(t *testing.T) { + url, _ := freshAPIServer(t) + created := createHostAPI(t, url, "unprobed-host", "production") + idStr := created["id"].(string) + + got := getHostDetail(t, url, idStr) + if got.Liveness != nil { + t.Errorf("liveness = %+v, want nil for unprobed host", got.Liveness) + } + // compliance_summary still present (default zeros), not null. + if got.ComplianceSummary.Total != 0 { + t.Errorf("compliance_summary.total = %d, want 0", got.ComplianceSummary.Total) + } + }) +} + +// @ac AC-15 +// AC-15: host with mixed host_rule_state → compliance_summary counts +// match. +func TestHosts_GetByID_Enrichment_ComplianceSummaryCounts(t *testing.T) { + t.Run("api-hosts/AC-15", func(t *testing.T) { + url, pool := freshAPIServer(t) + created := createHostAPI(t, url, "mixed-host", "production") + idStr := created["id"].(string) + hostID, _ := uuid.Parse(idStr) + seedRuleStateForHost(t, pool, hostID, "rule.p1", "pass") + seedRuleStateForHost(t, pool, hostID, "rule.p2", "pass") + seedRuleStateForHost(t, pool, hostID, "rule.f1", "fail") + seedRuleStateForHost(t, pool, hostID, "rule.f2", "fail") + seedRuleStateForHost(t, pool, hostID, "rule.f3", "fail") + seedRuleStateForHost(t, pool, hostID, "rule.s1", "skipped") + seedRuleStateForHost(t, pool, hostID, "rule.e1", "error") + + got := getHostDetail(t, url, idStr) + s := got.ComplianceSummary + if s.Passing != 2 { + t.Errorf("passing = %d, want 2", s.Passing) + } + if s.Failing != 3 { + t.Errorf("failing = %d, want 3", s.Failing) + } + if s.Skipped != 1 { + t.Errorf("skipped = %d, want 1", s.Skipped) + } + if s.Error != 1 { + t.Errorf("error = %d, want 1", s.Error) + } + if s.Total != 7 { + t.Errorf("total = %d, want 7", s.Total) + } + }) +} + +// @ac AC-16 +// AC-16: host with no host_rule_state rows → compliance_summary all zeros, not null, not error. +func TestHosts_GetByID_Enrichment_ComplianceSummaryZerosOnEmpty(t *testing.T) { + t.Run("api-hosts/AC-16", func(t *testing.T) { + url, _ := freshAPIServer(t) + created := createHostAPI(t, url, "no-rules-host", "production") + idStr := created["id"].(string) + + got := getHostDetail(t, url, idStr) + s := got.ComplianceSummary + // All zero, valid JSON object (not null), no error. + if s.Passing != 0 || s.Failing != 0 || s.Skipped != 0 || s.Error != 0 || s.Total != 0 { + t.Errorf("got %+v, want all zeros", s) + } + }) +} diff --git a/app/internal/server/api_hosts_test.go b/app/internal/server/api_hosts_test.go index c2ecae33..2605d2bc 100644 --- a/app/internal/server/api_hosts_test.go +++ b/app/internal/server/api_hosts_test.go @@ -293,11 +293,13 @@ func TestHosts_GetByID(t *testing.T) { resp.Body.Close() t.Fatalf("get status = %d body=%s", resp.StatusCode, b) } - var got map[string]any + var got struct { + Host map[string]any `json:"host"` + } _ = json.NewDecoder(resp.Body).Decode(&got) resp.Body.Close() - if got["hostname"] != "by-id" { - t.Errorf("hostname = %v, want by-id", got["hostname"]) + if got.Host["hostname"] != "by-id" { + t.Errorf("host.hostname = %v, want by-id", got.Host["hostname"]) } // Unknown id → 404. @@ -388,11 +390,13 @@ func TestHosts_Patch_InvalidIP(t *testing.T) { // Confirm IP wasn't mutated by re-reading. req = asRole(t, "GET", url+"/api/v1/hosts/"+id, auth.RoleAdmin, nil) resp = doReq(t, req) - var got map[string]any + var got struct { + Host map[string]any `json:"host"` + } _ = json.NewDecoder(resp.Body).Decode(&got) resp.Body.Close() - if got["ip_address"] != origIP { - t.Errorf("ip_address mutated despite 400: %v vs %v", got["ip_address"], origIP) + if got.Host["ip_address"] != origIP { + t.Errorf("ip_address mutated despite 400: %v vs %v", got.Host["ip_address"], origIP) } }) } diff --git a/app/internal/server/hosts_enrichment.go b/app/internal/server/hosts_enrichment.go new file mode 100644 index 00000000..6b999ce3 --- /dev/null +++ b/app/internal/server/hosts_enrichment.go @@ -0,0 +1,74 @@ +package server + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/google/uuid" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + + "github.com/Hanalyx/openwatch/internal/server/api" +) + +// loadHostLiveness reads the host_liveness row for the given host, or +// returns (nil, nil) when no row exists. Spec api-hosts C-05 / AC-13 / +// AC-14. +func loadHostLiveness(ctx context.Context, pool *pgxpool.Pool, hostID uuid.UUID) (*api.HostLiveness, error) { + const q = ` + SELECT reachability_status, last_probe_at, last_response_ms, + consecutive_failures, last_state_change_at, last_error_type + FROM host_liveness + WHERE host_id = $1` + var ( + status string + lastProbeAt *time.Time + lastResponseMS *int + consecutiveFails int + lastStateChangeAt *time.Time + lastErrorType *string + ) + err := pool.QueryRow(ctx, q, hostID).Scan( + &status, &lastProbeAt, &lastResponseMS, + &consecutiveFails, &lastStateChangeAt, &lastErrorType, + ) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, nil // AC-14: no liveness row → null in response + } + return nil, fmt.Errorf("loadHostLiveness: %w", err) + } + out := &api.HostLiveness{ + ReachabilityStatus: api.HostLivenessReachabilityStatus(status), + ConsecutiveFailures: &consecutiveFails, + LastProbeAt: lastProbeAt, + LastResponseMs: lastResponseMS, + LastStateChangeAt: lastStateChangeAt, + LastErrorType: lastErrorType, + } + return out, nil +} + +// loadHostComplianceSummary reads the per-status counts from +// host_rule_state for the given host. A host with no rule_state rows +// returns all zeros — never an error. Spec api-hosts C-06 / AC-16. +func loadHostComplianceSummary(ctx context.Context, pool *pgxpool.Pool, hostID uuid.UUID) (api.HostComplianceSummary, error) { + const q = ` + SELECT + COUNT(*) FILTER (WHERE current_status = 'pass')::BIGINT AS passing, + COUNT(*) FILTER (WHERE current_status = 'fail')::BIGINT AS failing, + COUNT(*) FILTER (WHERE current_status = 'skipped')::BIGINT AS skipped, + COUNT(*) FILTER (WHERE current_status = 'error')::BIGINT AS errors, + COUNT(*)::BIGINT AS total + FROM host_rule_state + WHERE host_id = $1` + var s api.HostComplianceSummary + if err := pool.QueryRow(ctx, q, hostID).Scan( + &s.Passing, &s.Failing, &s.Skipped, &s.Error, &s.Total, + ); err != nil { + return api.HostComplianceSummary{}, fmt.Errorf("loadHostComplianceSummary: %w", err) + } + return s, nil +} diff --git a/app/internal/server/hosts_handlers.go b/app/internal/server/hosts_handlers.go index bc3bd734..9fe82d02 100644 --- a/app/internal/server/hosts_handlers.go +++ b/app/internal/server/hosts_handlers.go @@ -108,13 +108,16 @@ func (h *handlers) PostHosts(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusCreated, hostResponse(created)) } -// GetHostByID fetches a host. -// Spec api-hosts AC-08. +// GetHostByID fetches a host with liveness + compliance_summary enrichment. +// Spec api-hosts AC-08, AC-13, AC-14, AC-15, AC-16. func (h *handlers) GetHostByID(w http.ResponseWriter, r *http.Request, id openapitypes.UUID) { if denied := auth.EnforcePermission(w, r, auth.HostRead); denied { return } - got, err := h.hosts.GetByID(r.Context(), uuid.UUID(id)) + ctx := r.Context() + hostID := uuid.UUID(id) + + got, err := h.hosts.GetByID(ctx, hostID) if err != nil { if errors.Is(err, host.ErrHostNotFound) { writeError(w, http.StatusNotFound, "hosts.not_found", "client", @@ -125,7 +128,26 @@ func (h *handlers) GetHostByID(w http.ResponseWriter, r *http.Request, id openap "lookup failed", true) return } - writeJSON(w, http.StatusOK, hostResponse(got)) + + liveness, err := loadHostLiveness(ctx, h.pool, hostID) + if err != nil { + writeError(w, http.StatusInternalServerError, "server.error", "server", + "liveness lookup failed", true) + return + } + summary, err := loadHostComplianceSummary(ctx, h.pool, hostID) + if err != nil { + writeError(w, http.StatusInternalServerError, "server.error", "server", + "compliance summary lookup failed", true) + return + } + + resp := api.HostDetailResponse{ + Host: hostResponse(got), + Liveness: liveness, + ComplianceSummary: summary, + } + writeJSON(w, http.StatusOK, resp) } // PatchHostByID updates mutable host fields. diff --git a/app/specs/api/hosts.spec.yaml b/app/specs/api/hosts.spec.yaml index 228dfe08..8668e7bf 100644 --- a/app/specs/api/hosts.spec.yaml +++ b/app/specs/api/hosts.spec.yaml @@ -1,7 +1,7 @@ spec: id: api-hosts - title: Host inventory CRUD HTTP surface - version: "1.0.0" + title: Host inventory CRUD HTTP surface + Slice B enrichments + version: "1.1.0" status: approved tier: 2 @@ -31,11 +31,12 @@ spec: - GET / POST /hosts - GET / PATCH / DELETE /hosts/{id} - "Filters: ?environment=..., ?tag=..." + - "GET /hosts/{id} response carries optional liveness sub-object (host_liveness LEFT JOIN) — v1.1.0" + - "GET /hosts/{id} response carries compliance_summary sub-object (passing/failing/total derived from host_rule_state) — v1.1.0" excludes: - "Bulk import — deferred to A.5" - "Connectivity-check trigger — needs scan executor, deferred" - - "Joined reads (scan summary, last seen) — Slice B" - - "Cursor pagination + multi-tag — Slice B" + - "Cursor pagination + multi-tag — deferred" constraints: - id: C-01 @@ -54,6 +55,14 @@ spec: description: PATCH MUST NOT change hostname, created_by, created_at, or id (immutable in Slice A); only the patchable fields the UpdateParams struct exposes may be mutated type: technical enforcement: error + - id: C-05 + description: GET /hosts/{id} response MUST include the host's liveness as either a populated sub-object (when host_liveness has a row) or null (when no probe has run yet); reachability values are constrained to the host_liveness CHECK enum (reachable/unreachable/unknown) + type: technical + enforcement: error + - id: C-06 + description: GET /hosts/{id} response MUST include a compliance_summary sub-object with passing, failing, skipped, error, and total counts derived from host_rule_state. A host with no rule_state rows MUST receive zeros — never null and never an error + type: technical + enforcement: error acceptance_criteria: - id: AC-01 @@ -100,3 +109,19 @@ spec: description: DELETE /hosts/{id} with a caller missing host:delete returns 403; the row is NOT soft-deleted. priority: critical references_constraints: [C-02] + - id: AC-13 + description: GET /hosts/{id} for a host that has a host_liveness row returns 200 with liveness.reachability_status equal to the stored value and liveness.last_probe_at populated. + priority: critical + references_constraints: [C-05] + - id: AC-14 + description: GET /hosts/{id} for a host that has no host_liveness row returns 200 with liveness=null — never an error, never a synthesised default. + priority: critical + references_constraints: [C-05] + - id: AC-15 + description: GET /hosts/{id} for a host with seeded host_rule_state rows returns 200 with compliance_summary populated with the correct passing, failing, skipped, error, and total counts. + priority: critical + references_constraints: [C-06] + - id: AC-16 + description: GET /hosts/{id} for a host with no host_rule_state rows returns 200 with compliance_summary populated with all zero counts — never null and never an error. + priority: critical + references_constraints: [C-06]