diff --git a/.dockerignore b/.dockerignore
index b1ba248..47b179f 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -10,6 +10,8 @@
**/node_modules
**/dist
artifacts
+.local-appdata
+.local-test-results
data
backups
imports
diff --git a/.gitignore b/.gitignore
index 8b4779c..c697d99 100644
--- a/.gitignore
+++ b/.gitignore
@@ -89,6 +89,7 @@ Thumbs.db
*.backup
# Local logs
+/.local-test-results/
*.log
logs/
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 11aadbc..f0e6d12 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -3,6 +3,9 @@
true
+
+
+
diff --git a/PUBLIC_SOURCE_EXPORT.md b/PUBLIC_SOURCE_EXPORT.md
index d9d2f48..4942f86 100644
--- a/PUBLIC_SOURCE_EXPORT.md
+++ b/PUBLIC_SOURCE_EXPORT.md
@@ -1,3 +1,3 @@
# Public source export
-This source snapshot was generated from the private canonical repository at revision `883ef812ee04516208b84972195b5e2a46207e44`. It contains no private Git history or operational evidence. Changes are published from the canonical repository.
+This source snapshot was generated from the private canonical repository at revision `2ed833f290b439a7bc91e5fd19b042c269e3bee9`. It contains no private Git history or operational evidence. Changes are published from the canonical repository.
diff --git a/PUBLIC_SOURCE_MANIFEST.json b/PUBLIC_SOURCE_MANIFEST.json
index 81c5364..ede90bf 100644
--- a/PUBLIC_SOURCE_MANIFEST.json
+++ b/PUBLIC_SOURCE_MANIFEST.json
@@ -1,4121 +1,5658 @@
{
"schemaVersion": 1,
- "sourceRevision": "883ef812ee04516208b84972195b5e2a46207e44",
+ "sourceRevision": "2ed833f290b439a7bc91e5fd19b042c269e3bee9",
"files": [
{
"path": ".config/dotnet-tools.json",
"sha256": "0d705a52e2d0a8c18776b41cd09b69955c6d265848f2fc7c913ae17e72924e07",
- "bytes": 183
+ "bytes": 183,
+ "mode": "100644"
},
{
"path": ".dockerignore",
- "sha256": "c2ea27f6d9d98d326aa1bd05493e30641f7f33eab2c508f7a65a09b1dcb3291e",
- "bytes": 186
+ "sha256": "1a0e5ead63b64012f502a7832904c56567f4848125496e646aa35a63f1cd6506",
+ "bytes": 221,
+ "mode": "100644"
},
{
"path": ".editorconfig",
"sha256": "2ab44261f0604203d49d28099ae623e7430562434dbf08d98c078b3b38c59a09",
- "bytes": 989
+ "bytes": 989,
+ "mode": "100644"
},
{
"path": ".env.example",
"sha256": "7ee366af82c106c53da2aa2eda7b1da9a71681b2c800159161c0addc84f4dd23",
- "bytes": 861
+ "bytes": 861,
+ "mode": "100644"
},
{
"path": ".gitattributes",
"sha256": "d3241577d687fda1ad4c3a313d9453af1d501ab6232c8ae925d5da1bcbc76a22",
- "bytes": 152
+ "bytes": 152,
+ "mode": "100644"
},
{
"path": ".gitignore",
- "sha256": "063f777aadb4464b25b0a7e7490d23941191dc5bc1fa63a7219cd0825fd7bc90",
- "bytes": 2564
+ "sha256": "1b3ed68d19aefc7de74cb9c00892daecbc38fcad66b4c75086b56444702f22bb",
+ "bytes": 2586,
+ "mode": "100644"
},
{
"path": "CONTRIBUTING.md",
"sha256": "555837ab788bf1ab8c826f928a3bfd813be1e26383bbf7ba3b14bd26074cf629",
- "bytes": 1422
+ "bytes": 1422,
+ "mode": "100644"
},
{
"path": "Directory.Build.props",
"sha256": "1b9316a55c61a41e2bea70fe860dc4cd6dc225b7506d8b25b9b764df97609e6e",
- "bytes": 427
+ "bytes": 427,
+ "mode": "100644"
},
{
"path": "Directory.Build.targets",
"sha256": "f2a1cc660c5c59233273cbc070145eb5892877c5de2881be6c0a2bed32e624b1",
- "bytes": 391
+ "bytes": 391,
+ "mode": "100644"
},
{
"path": "Directory.Packages.props",
- "sha256": "4de6b529b4e61eed6b3204d0f148f325f8c432f549cde58c961cbb986ba9ccdf",
- "bytes": 2270
+ "sha256": "7ae6aa4eb18f5d36251492406a9131b0c907d178c23a41f6808b1981879629ca",
+ "bytes": 2539,
+ "mode": "100644"
},
{
"path": "ItWorx.KnowledgeHub.slnx",
"sha256": "e33988c92c1615bc225920cb466069a8e6d118ab6501d564de0766304a173d27",
- "bytes": 1057
+ "bytes": 1057,
+ "mode": "100644"
},
{
"path": "LICENSE",
"sha256": "3cb8980a332769231620c69ab23929cafc5fbb98664f19c3c623b53ded5136d4",
- "bytes": 377
+ "bytes": 377,
+ "mode": "100644"
},
{
"path": "MANIFEST.json",
"sha256": "9e5bcade00174e302dd665220aa911c0f23d2d377c7fb5a8e0ee3cfdaec6a425",
- "bytes": 901
+ "bytes": 901,
+ "mode": "100644"
},
{
"path": "NuGet.Config",
"sha256": "90571d3c6c59fed0ceb64eca618a17c24c0d2eb05d67fa742f696eb9cf0c54d1",
- "bytes": 275
+ "bytes": 275,
+ "mode": "100644"
},
{
"path": "README.md",
- "sha256": "f96c14c96b82be0cda9a3b10638a88e3d8507346a62360a9e8b7924dc19d144e",
- "bytes": 6085
+ "sha256": "8d496d4b14cd4a52b64a448abc40a91c1f736a3ed2a769eac04c4d24aa863df7",
+ "bytes": 6433,
+ "mode": "100644"
},
{
"path": "SECURITY.md",
"sha256": "fdfe1a8563115cff5bc83c898b6afc436494b4927690884425fc0b1372ab088d",
- "bytes": 2165
+ "bytes": 2165,
+ "mode": "100644"
},
{
"path": "deploy/docker/Dockerfile",
- "sha256": "844bdabab7f7d7f96867c98a20a4018e88ed54116dc26c643c4dcd8505e866c8",
- "bytes": 3525
+ "sha256": "fbffbb204ce9d9a82c73fae581da469081b72bd1ea9ba94affe856b271bbb5e5",
+ "bytes": 3688,
+ "mode": "100644"
+ },
+ {
+ "path": "deploy/observability/collector.yaml",
+ "sha256": "ee5a4c363a25c5d41e155100a2212b845167ba6895bae6be9981faa5fa179fe1",
+ "bytes": 386,
+ "mode": "100644"
+ },
+ {
+ "path": "deploy/observability/compose.yaml",
+ "sha256": "13c05bed53d4861190ba18ac4b82eb229bb46ed157a7cb82eabe9c403a2bb729",
+ "bytes": 1952,
+ "mode": "100644"
+ },
+ {
+ "path": "deploy/observability/grafana-datasource.yaml",
+ "sha256": "c34f5444422f7a61018eb9e6d33448069537e995b980d4972ced3fc1228b6865",
+ "bytes": 173,
+ "mode": "100644"
+ },
+ {
+ "path": "deploy/observability/prometheus.yaml",
+ "sha256": "2fe4898dd5b505e6163c73a41504c87dec733a98ad5618c75ff80ab805bfe9da",
+ "bytes": 168,
+ "mode": "100644"
},
{
"path": "docker-compose.yml",
- "sha256": "86ae75c4cb9cc9374a888c42a9e99912614f127fd738873a51ae5eea72071495",
- "bytes": 4298
+ "sha256": "79def4bd786305c260005ec3b0b5d3d96f0d5c7c26de2be8e4b5787fafe48ff9",
+ "bytes": 4603,
+ "mode": "100644"
},
{
"path": "docs/00-product-vision.md",
"sha256": "e6a4cac238971be1a501cbbe115c530b8e7623071566ce7a47ecfab08bf0516d",
- "bytes": 3029
+ "bytes": 3029,
+ "mode": "100644"
},
{
"path": "docs/01-scope-and-non-goals.md",
"sha256": "4cb84268970b866251c7e3c9cd947b4dd2aebfd099e77cf7753715be8b29a999",
- "bytes": 2976
+ "bytes": 2976,
+ "mode": "100644"
},
{
"path": "docs/05-non-functional-requirements.md",
"sha256": "ae191ed4ebade9b90bcd45b5cbd4df6b261ef861f4eb53756956235d0cec9f3b",
- "bytes": 4195
+ "bytes": 4195,
+ "mode": "100644"
},
{
"path": "docs/06-system-architecture.md",
"sha256": "99ca9fec4eb3262e17c7f63341bd2b3e8cb15b46e0b60ce69196bbdfc5c3b3ab",
- "bytes": 5193
+ "bytes": 5193,
+ "mode": "100644"
},
{
"path": "docs/07-domain-model.md",
"sha256": "1429e4f543db02a325d2415eec1a7c50d3caca1afa0b670581aa4352356a0331",
- "bytes": 6054
+ "bytes": 6054,
+ "mode": "100644"
},
{
"path": "docs/08-database-schema.md",
"sha256": "d507da527686acfe4f7bcbf0b575169c701c0c08570153466171553c3b3eb8fb",
- "bytes": 6443
+ "bytes": 6443,
+ "mode": "100644"
},
{
"path": "docs/09-api-contract.md",
"sha256": "9ca68276ea38bb48c3dfc18a365d01beeec7f2c2d6f3de71cf7056b1a7acc64b",
- "bytes": 6415
+ "bytes": 6415,
+ "mode": "100644"
},
{
"path": "docs/10-search-and-indexing.md",
"sha256": "1fda26e0adea0bf4ded57150d99f41c09bfbfc8f42ba071ad525ec98d4a76e8b",
- "bytes": 3733
+ "bytes": 3733,
+ "mode": "100644"
},
{
"path": "docs/11-markdown-and-linking.md",
"sha256": "35aeb470602585faf2fe8f218c4a9dca4291bd7f7f1d8160bfa7dfbdf25ca5da",
- "bytes": 4779
+ "bytes": 4779,
+ "mode": "100644"
},
{
"path": "docs/12-auth-security.md",
- "sha256": "8c1eb011da918e17dc363facbe191233d4c4bdedc4b755c58895eaa1d0ebbf29",
- "bytes": 6738
+ "sha256": "fbf59555c82aa9c77cd4a036ee474b854795c7644c9201f7dc2090ae865a6ea8",
+ "bytes": 7202,
+ "mode": "100644"
},
{
"path": "docs/13-attachments.md",
"sha256": "aa0ad35374ec3ac984a7e14a154cf0f256c956128d8b26e8e55670735d24ae91",
- "bytes": 4262
+ "bytes": 4262,
+ "mode": "100644"
},
{
"path": "docs/14-revisions-and-audit.md",
"sha256": "ae53e26efc9c3c19d7b47d477f2e6f0c9a0d429e2a089c59e8b603140cc05e12",
- "bytes": 3462
+ "bytes": 3462,
+ "mode": "100644"
},
{
"path": "docs/15-project-hub.md",
"sha256": "f94b371aba66207d7cf5a7f82d7592745530a244a4f7b811e667efc99aacaa1c",
- "bytes": 3561
+ "bytes": 3561,
+ "mode": "100644"
},
{
"path": "docs/16-runbooks-and-adrs.md",
"sha256": "6d075534795ca26e02d3bd5167c2e085411e6a13934203267e6929297755f90a",
- "bytes": 2455
+ "bytes": 2455,
+ "mode": "100644"
},
{
"path": "docs/17-leafwiki-migration.md",
"sha256": "2d812dddf87a09bbd5494fb23109999ea199fee885877b441b1fb4776e2d22da",
- "bytes": 9220
+ "bytes": 9220,
+ "mode": "100644"
},
{
"path": "docs/18-backup-restore.md",
- "sha256": "644a777f5d4e595f1af7e79807581a06248c182baa79117189eeeab7a227c676",
- "bytes": 4762
+ "sha256": "c4ae783f1f98441064a696fc02924369a267649ebcf86a2f05a29b0169ac76ee",
+ "bytes": 9340,
+ "mode": "100644"
},
{
"path": "docs/19-observability.md",
"sha256": "62410475a7ac1c4eaea3bdc56041f9075323deb31a0e0458d3e3d0879d27876a",
- "bytes": 3925
+ "bytes": 3925,
+ "mode": "100644"
},
{
"path": "docs/20-ui-ux-design-system.md",
"sha256": "95d30a48c4e873ec0eba70c312a7a547a31b8319ae5cd66e579db81167a3c1f0",
- "bytes": 5117
+ "bytes": 5117,
+ "mode": "100644"
},
{
"path": "docs/21-accessibility.md",
"sha256": "d97b7cb4bc04c8392e78ce93a5d2a10b6c6b27f2ea89febfe8889a17991c516a",
- "bytes": 3668
+ "bytes": 3668,
+ "mode": "100644"
},
{
"path": "docs/22-testing-strategy.md",
"sha256": "3640e34604bb230378f210ebdfa8993218640f73034cc1a98c0abd20a36cc7ca",
- "bytes": 5038
+ "bytes": 5038,
+ "mode": "100644"
},
{
"path": "docs/24-mcp-readiness.md",
"sha256": "016ece2dfa1b812443d01554b9887eebf8251d9608d872677fdf49c120ac5862",
- "bytes": 5511
+ "bytes": 5511,
+ "mode": "100644"
},
{
"path": "docs/26-threat-model.md",
"sha256": "6a4e3d1fa28a4e74afa5f202bc41fe40ed9be7ecfe4e64a03b18d4d43d0d64d9",
- "bytes": 4989
+ "bytes": 4989,
+ "mode": "100644"
},
{
"path": "docs/api/openapi.yaml",
- "sha256": "ed9a4cc30e22bd08b106c3580be8321e693e1acce55f9948983265d88fee8aa3",
- "bytes": 209363
+ "sha256": "5b8fe80c77639635e49a7d555c64978dc5a3453dd8cf18984edaf59a774bb154",
+ "bytes": 232353,
+ "mode": "100644"
},
{
"path": "docs/assets/screenshots/02-dashboard.png",
"sha256": "4f77968a98c1c617c99e652919c6855328a91e0293951011348560170cd0c88b",
- "bytes": 120018
+ "bytes": 120018,
+ "mode": "100644"
},
{
"path": "docs/assets/screenshots/03-knowledge.png",
"sha256": "a84f7fe07708d67479c660775ea0bb6193d4c37ff9d16594b98326478337dd73",
- "bytes": 190279
+ "bytes": 190279,
+ "mode": "100644"
+ },
+ {
+ "path": "docs/background-jobs.md",
+ "sha256": "df2300a912181cbc4e67ea613bd08aded5d4741bc4f3b81873ea1886c6ba96ae",
+ "bytes": 2714,
+ "mode": "100644"
},
{
"path": "docs/guides/back-ups-herstel-en-unraid-beheer.md",
- "sha256": "c9144dbc479e0bc97285f8bd75ced327643450dc19ea08b59c708b91d5027781",
- "bytes": 5641
+ "sha256": "236221ce56c2c9db6612cb61375138fabb8662d4f69034e788599f1563d5d73b",
+ "bytes": 7358,
+ "mode": "100644"
},
{
"path": "docs/guides/bijlagen-en-leafwiki-import.md",
"sha256": "c6a2cf6903cdf499c5a02d34459b00d13f426c08ab915dfb422cf8e376b72dce",
- "bytes": 3591
+ "bytes": 3591,
+ "mode": "100644"
},
{
"path": "docs/guides/documenten-toevoegen-en-intake.md",
"sha256": "2352f8ef10155ddd4143c9d2525cae80a9d64676dd93cca8c07c13716716b932",
- "bytes": 1650
+ "bytes": 1650,
+ "mode": "100644"
},
{
"path": "docs/guides/eerste-installatie-en-aanmelden.md",
"sha256": "73038719d7f1f38b5f65748387fe018e64c3877a1a1052c2b02133b529480ab7",
- "bytes": 3783
+ "bytes": 3783,
+ "mode": "100644"
},
{
"path": "docs/guides/operationele-runbooks-overzicht.md",
"sha256": "572d982a90ac4ee02f6755bbaaafe20fae5a897365a1c6496ac1e216c5389dfc",
- "bytes": 5067
+ "bytes": 5067,
+ "mode": "100644"
},
{
"path": "docs/guides/pagina-s-schrijven-en-beheren.md",
"sha256": "b16c8d3a553b7c1eb6a3ce9a0157eddf2cf6583fd5ff816cfc8a2310feca6ee0",
- "bytes": 5103
+ "bytes": 5103,
+ "mode": "100644"
},
{
"path": "docs/guides/projecten-beslissingen-en-runbooks.md",
"sha256": "81ff1b257dd388b5128e4231c9374da54f7faaca32f86564d8e4887bfcb89311",
- "bytes": 4428
+ "bytes": 4428,
+ "mode": "100644"
},
{
"path": "docs/guides/service-accounts-en-automatisering.md",
"sha256": "4656bea026e6adeffffb1dbdcdb0572a2964290c9fd122794093a241f9f4d19c",
- "bytes": 7449
+ "bytes": 7449,
+ "mode": "100644"
},
{
"path": "docs/guides/source-batch-removal.md",
"sha256": "04d3c6ddebd650fa3a5159c7dcaf8f8c1fca83c1fbaefb0ab6375b3d9e029536",
- "bytes": 2105
+ "bytes": 2105,
+ "mode": "100644"
},
{
"path": "docs/guides/zoeken-geschiedenis-en-labels.md",
"sha256": "0abaade24d0d88e2c05f94263a1e213680ba3f371b6ffc566abcc3c8a855c3d2",
- "bytes": 2909
+ "bytes": 2909,
+ "mode": "100644"
+ },
+ {
+ "path": "docs/observability-platform.md",
+ "sha256": "024e18655832b00a3e9a7033da3f445d4eb83174994ef5fdaf94f5c2411cd984",
+ "bytes": 2624,
+ "mode": "100644"
+ },
+ {
+ "path": "docs/platform-settings.md",
+ "sha256": "aecb3c59da8c668f829538049f4507e56c30c142d45161a3bdaf56870dcc340a",
+ "bytes": 3387,
+ "mode": "100644"
},
{
"path": "fixtures/leafwiki/README.md",
"sha256": "83d8c74ad07e21f79745348d9c7b4853561cf15e3910861bc0ef4c6d410cf29f",
- "bytes": 621
+ "bytes": 621,
+ "mode": "100644"
},
{
"path": "fixtures/markdown-import/Projecten/Voorbeeldproject.md",
"sha256": "8073c9e060711df344c89992ae1d164c29cccd2362658f0281802bd873b7c3be",
- "bytes": 162
+ "bytes": 162,
+ "mode": "100644"
},
{
"path": "fixtures/markdown-import/School/Linux/Bestandsrechten.md",
"sha256": "102d22fe7096f0249200f9a492741bfbfc3940d8f488a7ffd766f9f85b4cf22f",
- "bytes": 126
+ "bytes": 126,
+ "mode": "100644"
},
{
"path": "fixtures/markdown-import/School/Linux/Shell-operators.md",
"sha256": "40ad436d12a4a5b89855a9fce472bb269f8b72cb57f4f2207a5c1e3b8f4d9573",
- "bytes": 202
+ "bytes": 202,
+ "mode": "100644"
},
{
"path": "fixtures/markdown-import/assets/architecture.svg",
"sha256": "172e951e869735f4ca68f3aa68945bdff7e7868561292266908c77bfc61efc77",
- "bytes": 575
+ "bytes": 575,
+ "mode": "100644"
},
{
"path": "fixtures/markdown-import/source-manifest.json",
"sha256": "7028b1284fd7e2910dcbc08cf5e50515ab405eab66b006decb7556736800abdc",
- "bytes": 140
+ "bytes": 140,
+ "mode": "100644"
},
{
"path": "global.json",
"sha256": "6cd80ed6f7a93e76c20e47164e3bfedfddc1b42b33519b2cd2dee6febff4e836",
- "bytes": 111
+ "bytes": 111,
+ "mode": "100644"
},
{
"path": "package.json",
"sha256": "54f467b65e7c45cd7e16d65e36dadef42c8d14f6b9c0a105a57703caa6d8c64b",
- "bytes": 1042
+ "bytes": 1042,
+ "mode": "100644"
},
{
"path": "pnpm-lock.yaml",
- "sha256": "128ec7f17a519778986a9d7890ec2da74fd6362fbeaae241df6ff0589be42b22",
- "bytes": 139775
+ "sha256": "5139471372511d950cccb7dffd9346c9d8bca5592dbd99f96a54c359b4c78872",
+ "bytes": 140522,
+ "mode": "100644"
},
{
"path": "pnpm-workspace.yaml",
"sha256": "974c5c67f9c2429b38eeea3cc1a7c8d78e5c24833c647c5c69e6a7b369d53931",
- "bytes": 150
+ "bytes": 150,
+ "mode": "100644"
},
{
"path": "scripts/check-openapi-coverage.mjs",
- "sha256": "064f649140e7c72234680881bf59051053d4df8fb36a2eb3d84712a31b328508",
- "bytes": 4122
+ "sha256": "3f43d6b228336be887144e67175b03614792e5f08967069fae5f5c3d09046cd4",
+ "bytes": 2748,
+ "mode": "100644"
},
{
"path": "scripts/doctor.mjs",
"sha256": "32f7f374e277ef82e1cf66c4f05d029d6c60bb7ac9170ad71f7bc4886f5ec48c",
- "bytes": 3401
+ "bytes": 3401,
+ "mode": "100644"
},
{
"path": "scripts/generate-release-manifest.mjs",
- "sha256": "5d801b61d2a805c5d4b20af8520193b90821296f9e5cc7851f12767a91113a52",
- "bytes": 5138
+ "sha256": "cb2efde98ed83182a340a9c426cfcfadec84167cad8f713636c7f89e1a735405",
+ "bytes": 5981,
+ "mode": "100644"
},
{
"path": "scripts/lib/exec.mjs",
"sha256": "17b303d2fec7c6b99eb687036fac2658b2fdfa63cc3f60517b3cd96786210c0c",
- "bytes": 3493
+ "bytes": 3493,
+ "mode": "100644"
+ },
+ {
+ "path": "scripts/lib/openapi-routes.mjs",
+ "sha256": "b8bbe97f1e11572587655f548260684c7f72efafdcf993fb57165eac443c13b4",
+ "bytes": 1216,
+ "mode": "100644"
+ },
+ {
+ "path": "scripts/lib/product-backlog.mjs",
+ "sha256": "4f3b0fb8abbeca20b453ff386774ee42aad4ee2d2e71427e1a5cfb44795dc8bd",
+ "bytes": 2576,
+ "mode": "100644"
+ },
+ {
+ "path": "scripts/lib/release-gates.mjs",
+ "sha256": "82d3c3edac64849e8777a6d19189d67539f60137a4056a0f506a1951f97c69f0",
+ "bytes": 260,
+ "mode": "100644"
+ },
+ {
+ "path": "scripts/lib/route-map.mjs",
+ "sha256": "d08f5c5d9347ae97e19adccf0e4982df0a5ae25e722a5847ef048162f1fae3a1",
+ "bytes": 8948,
+ "mode": "100644"
},
{
"path": "scripts/validate-openapi.mjs",
"sha256": "d59e7aa97e5c3bf17438804453306900d986f07dab811ee59e03af62a11383ba",
- "bytes": 2209
+ "bytes": 2209,
+ "mode": "100644"
},
{
"path": "scripts/validate-public-source.mjs",
- "sha256": "c6610b35e3938330ae4deda6bfd5b7f4c97ce547b4a94cf9a39faecc8da6c053",
- "bytes": 1238
+ "sha256": "3ae5d677cff57dc431f0d000c223f9efd1648c69e74b0b1a0a4bde207d57e76a",
+ "bytes": 3043,
+ "mode": "100644"
},
{
"path": "scripts/verify.mjs",
- "sha256": "3e47ed409ecb43b8a1e6237d60a80d8b3d261a5e5657cd8c1dbd215fd855ef1a",
- "bytes": 8548
+ "sha256": "d327338d63c2f5a016e5e7d0e0c621d43f808ba682eed1843e8ab3f1ad7df469",
+ "bytes": 11998,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Authorization/AuthorizationPolicies.cs",
"sha256": "3db009c090e34b8386c9d9e2169f0546b4e545e4b35c46d8b50e868ff126da1f",
- "bytes": 401
+ "bytes": 401,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Authorization/ContentAccessAuthorizationHandler.cs",
"sha256": "4e29ba7d31db43611a201c3a5332ab2a5746e983303fa25c33ec56d489377916",
- "bytes": 2212
+ "bytes": 2212,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Authorization/ContentAccessEndpointExtensions.cs",
"sha256": "60f2165830bfe08f13cdc13e1c5d26a414665d16f7bc97b488216851c29b8335",
- "bytes": 1517
+ "bytes": 1517,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Authorization/ContentAccessRequirement.cs",
"sha256": "e55e3427da9661f13c333c885df4757548ff96f0ce58de9d47babbb8d9075592",
- "bytes": 938
+ "bytes": 938,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Authorization/ServiceTokenAuthenticationHandler.cs",
"sha256": "95e281204f0c2f621f9f919000464073e3a7f318029fced25e7ed12cc57c5701",
- "bytes": 3810
+ "bytes": 3810,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Authorization/WorkspaceRoleAuthorizationHandler.cs",
"sha256": "265e80c23e957b90828f9c144a1541ea67c6639bbbf2626910618e36b805127a",
- "bytes": 1500
+ "bytes": 1500,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Authorization/WorkspaceRoleRequirement.cs",
"sha256": "a69cc4bb1d2a982e7dbdf352f5a65b43eeccd0185d4a56739746635750b79b7e",
- "bytes": 533
+ "bytes": 533,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Api/BackgroundServices/JobWorker.cs",
+ "sha256": "988b673691e5117646605acfd7f9164f97d94670a380865bc8c78381caf3cde0",
+ "bytes": 20861,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Api/BackgroundServices/OperationalTelemetryBackgroundService.cs",
+ "sha256": "a511ac85b6a9acf78faa9a13ae0453157cbe2aab2b574ab6f7be3939ca4a93e7",
+ "bytes": 2014,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/BackgroundServices/StagingSessionCleanupBackgroundService.cs",
"sha256": "a9627543eeb86d8e75e2c926fb0624efc99ba9679649184df2ad33c72480372b",
- "bytes": 4372
+ "bytes": 4372,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/ActivityEndpoints.cs",
- "sha256": "df0965011d7d97a6d03dbaf24a6c0bf8c05ed6be616a6bfbf78efdd090d04bfd",
- "bytes": 1150
+ "sha256": "d86fc65ef2acdd9367acc8c96298da5164791e448e8af3464635ac5bf31613ba",
+ "bytes": 1210,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/AdminAuditEndpoints.cs",
+ "sha256": "7bbdbaff9e3794cbbe2a1cee6c5344d82f2e57427756ab5252885590f5bb8634",
+ "bytes": 1086,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/AdminEndpoints.cs",
"sha256": "9ae490206424d5e4646bcef9a15df7938a082757ac39f1eb02d6dec9d325eeb0",
- "bytes": 9494
+ "bytes": 9494,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/AttachmentsEndpoints.cs",
"sha256": "7e4eef5e2d14040b4dfb602580794e20a72ca3d643b90054beabe68aefa1c8c8",
- "bytes": 10712
+ "bytes": 10712,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/AuthEndpoints.cs",
- "sha256": "cf9ff5fdb2cf00b815e3e042259a6dafc761c3439ffbe4d257df1aa6dcf223db",
- "bytes": 5598
+ "sha256": "d07a2b79dda0707d711666c6c97d07aca7313ba4b5d12e69551bbec0ba969d18",
+ "bytes": 5837,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/BackupsEndpoints.cs",
- "sha256": "f2fc4717f4376f10b51a16092b70fad1698a476b84d3fa84ff8e1aaf426c31d2",
- "bytes": 2288
+ "sha256": "4280b0353a2f71a15b90c6b2c674c1b87376f206a3e1bf222dbe161d8386ef6f",
+ "bytes": 7029,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/DataSourcesEndpoints.cs",
+ "sha256": "2134492af8e1fcd4a39dbcd8fbf884e6331d17d3147d03820083abf01e817b76",
+ "bytes": 5834,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/DecisionsEndpoints.cs",
"sha256": "eed41b036ef80a94909f30ecc748d0cd8c8b5410909faacdd93917d7290ef67d",
- "bytes": 10299
+ "bytes": 10299,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/DraftsEndpoints.cs",
"sha256": "c814c4bf4724ba1eaa8671bdf2f12eed0498933abb985b37c4cdce2b1fcfc917",
- "bytes": 4641
+ "bytes": 4641,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/ImportsEndpoints.cs",
"sha256": "7e5e95248d3fcb690e9675b8dfe2e2cfb97810c513a47ca39171f2711644f230",
- "bytes": 13588
+ "bytes": 13588,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/InfrastructureEndpoints.cs",
"sha256": "de8923f90ef62278f439bc6d7084133901b2b0dbb59984ae726430a829bd2b0e",
- "bytes": 30756
+ "bytes": 30756,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/IngestionProposalsEndpoints.cs",
- "sha256": "5fe075a816488b48149563041728724ffa22a9bb0bf1aa7261172d2dd11ffc8b",
- "bytes": 26519
+ "sha256": "017f865d012d6c603b293c9a83b57ebd7e7bffe030e042d94efa2707774d1193",
+ "bytes": 27349,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/JobsEndpoints.cs",
+ "sha256": "f985804dfb8d1263e957c69802931809bc9c14573d0f485e925c47d2a58bcfeb",
+ "bytes": 12964,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/PagesEndpoints.cs",
"sha256": "a043f77d8b7449bdb853f6326c021ffd358e5f3bd1a7af10643457d25db5488e",
- "bytes": 26245
+ "bytes": 26245,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/PlatformEndpoints.cs",
+ "sha256": "615fed1bad4c585a48153912834d76ea3fef4b90052e876e543e1d11736228b9",
+ "bytes": 11493,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/ProjectsEndpoints.cs",
"sha256": "bb715300847727e2e4905ccb652d28da4b167822d35827d611523bc4abf23d51",
- "bytes": 30000
+ "bytes": 30000,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/RagPublicationEndpoints.cs",
"sha256": "07dc72cab1fec0b8a8cf3915f741d5a21d73eb52d9e3b198e684499e92fcb7d0",
- "bytes": 3338
+ "bytes": 3338,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/RunbooksEndpoints.cs",
"sha256": "c39f45548034c21e2fcc7a9bf2cca469201c4382a0516419216413fa1a7da0be",
- "bytes": 9571
+ "bytes": 9571,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/SearchEndpoints.cs",
- "sha256": "e2b81a8d0245ee93e92c35bed5fcd9a9f14bb471e7829410e8006ba38326a45a",
- "bytes": 1968
+ "sha256": "e3c4b7f8219ac2f28b0d31a8005b7f8d8af2d0ab63c2559350f3d8fbe9aecf62",
+ "bytes": 2067,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/ServiceAccountsEndpoints.cs",
"sha256": "ac42ebec42c9cc8ddf5e55af651e1789084c514705157974bdcc7c0ebc3da452",
- "bytes": 6356
+ "bytes": 6356,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/SetupEndpoints.cs",
"sha256": "a2936b5f2be938e7564e6a6ce7001e9c3f7d600a320c6ce34bdf0535ed958150",
- "bytes": 3324
+ "bytes": 3324,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/SourceBatchPurgeEndpoints.cs",
"sha256": "0c0be832f9bc5eaf0352c40b396c97f56ff4d0ec1560409f85e68dc69802207e",
- "bytes": 3933
+ "bytes": 3933,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/TemplatesEndpoints.cs",
"sha256": "786f88c698d0c4e6544e196eb28d4bbdf68ebc46575bf8170e86467ec36ce606",
- "bytes": 972
+ "bytes": 972,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/UnifiedSearchEndpoints.cs",
+ "sha256": "30bb74b28623d99948fabcace4e893e57a400c4b69cb950aee8ed1162483b12a",
+ "bytes": 3648,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Endpoints/WorkspaceEndpoints.cs",
"sha256": "f32d69e9783cb3f7dd4dd1c238e7ea2d20af117820ba9c5c3626f41024cc02b8",
- "bytes": 1398
+ "bytes": 1398,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/ItWorx.KnowledgeHub.Api.csproj",
- "sha256": "7402928d3cde210690a5bd89bea05a432642346948a7c4b2ac2bcc424ea8a414",
- "bytes": 619
+ "sha256": "ec4d90148629b37868bffe3c3c3378ded0bb710ea1069541c179958b0507eae1",
+ "bytes": 872,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Knowledge/ETagHelper.cs",
"sha256": "f8068b81563a5035edcc674ed7489b4aedb5fec69fac2665121c80cb0af6d75d",
- "bytes": 593
+ "bytes": 593,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Operations/RestoreModeRunner.cs",
- "sha256": "ae0c3bbec69d2b667e8576f3b6384601fd003816e3cf133e3f05fde1ae5fbedb",
- "bytes": 3035
+ "sha256": "b03953c881b9d43f9e8a2ca14b5083e85fdaa88adcdb5cde34e1159ca0425624",
+ "bytes": 5993,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Program.cs",
- "sha256": "d3a6283ed7f30fe1a98d84fd934a28a0d2758b42eb4170bfb62d22778f74f6da",
- "bytes": 29884
+ "sha256": "112b628d5285a808296acfc97e5a52e63ef43e4fc6df87033443447f0f5954e1",
+ "bytes": 32882,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Properties/launchSettings.json",
"sha256": "ba7320d62133875f99cd089fc4f9f67c24c0d4d116c1df173f9d04261723b8f1",
- "bytes": 383
+ "bytes": 383,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Security/AntiforgeryEndpointFilter.cs",
"sha256": "6a307bcf239614f2d7fdcee0cd1928d17a64cbc666e45d9d9d44f7a593a261fd",
- "bytes": 1365
+ "bytes": 1365,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Security/CorrelationIdMiddleware.cs",
"sha256": "d93726d49e9cf09e92c41628c6edd772c0e9d35726f043ee1570dfe9d74d90f9",
- "bytes": 1232
+ "bytes": 1232,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Security/IdempotencyEndpointFilter.cs",
"sha256": "d2836a29097f6fd895e3713ea2d4ce68bcd4708133a3d2128a36cd626e6a4da0",
- "bytes": 8448
+ "bytes": 8448,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Security/RateLimitPolicies.cs",
- "sha256": "2dcceb1f6a0c5e6193135c93d9f34269c32cffb3c5d7fecf31a2c7f8a05ebaf2",
- "bytes": 406
+ "sha256": "4e63c03737cf82a2e3ef111b9b67a2322667a4f1edaf5e8892d17b550034c351",
+ "bytes": 503,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Security/SecurityHeadersMiddleware.cs",
"sha256": "e4392e92f0f6ed46b470580e7f9db6d3f80d80852cb21bcf63dbeaf8f569b99e",
- "bytes": 1153
+ "bytes": 1153,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/Security/ServiceTokenFailureRateLimiter.cs",
"sha256": "558bf8257f101c35bdde678dd10dea3419ccc956456d878270b0866f643f7e6a",
- "bytes": 1120
+ "bytes": 1120,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Api/Security/StrictAntiforgeryEndpointFilter.cs",
+ "sha256": "e596a295e30d7b1c8186a302027a3536b17404a97f773375707ce5cf492301ae",
+ "bytes": 1032,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/appsettings.Development.json",
"sha256": "4c9be8a8257311c69a2649815b0fb934e150a432a207232830b1978ffb552599",
- "bytes": 146
+ "bytes": 146,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/appsettings.json",
"sha256": "428f07dbd12b22ef80df16777488b2400dd7704fe8602985e53ef4903498f784",
- "bytes": 341
+ "bytes": 341,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Api/wwwroot/index.html",
"sha256": "a43bdd8b4f68310aaa40b88f311f139f8e957d31131c6994ddae5186c9ba5da1",
- "bytes": 1100
+ "bytes": 1100,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/AssemblyMarker.cs",
"sha256": "3ef21317427731204ba6383e07e26c34cabec72408aceba7213a00d7ce5e7d21",
- "bytes": 83
+ "bytes": 83,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Attachments/AttachmentContentLock.cs",
"sha256": "5f052641b844097a85939b65db9b4b1619c24ae857b9ac367f34026683fe616f",
- "bytes": 2953
+ "bytes": 2953,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Attachments/IAttachmentBlobStore.cs",
"sha256": "169751656a77b6263c739a62c48053f46d847f2f8636a43b426b582553682025",
- "bytes": 2251
+ "bytes": 2251,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Attachments/IAttachmentRepository.cs",
"sha256": "4451d0e709937e60402d1e1eb0d28e8f1444e6f471f050b9770a875f357d1cec",
- "bytes": 6662
+ "bytes": 6662,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Attachments/IContentSignatureDetector.cs",
"sha256": "627c43eafa0b8ee5b7d26eef87f247e1bf15cdff98d5829739b394ae7f4fd7a1",
- "bytes": 706
+ "bytes": 706,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Attachments/OrphanedBlobGarbageCollector.cs",
"sha256": "aa969b9f390d2c89663bfd7a2f4014a35146f5ac0ef98c80f37420266cbbf7fe",
- "bytes": 3023
+ "bytes": 3023,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Attachments/UseCases/GetAttachmentContentUseCase.cs",
"sha256": "bdde195b4b3cf8565a63a66b5699f7415be2403217cb0db4b386d837eced5ed8",
- "bytes": 6321
+ "bytes": 6321,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Attachments/UseCases/GetPageAttachmentsUseCase.cs",
"sha256": "ce94b1ff8f776a305dfca75d10a981252b402250e8a04b07511f03370dceac9f",
- "bytes": 852
+ "bytes": 852,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Attachments/UseCases/UploadAttachmentUseCase.cs",
"sha256": "f7ec6391e001dc256cb2f3cec4ee1bbb04975c6f5aaa951c160938c3eb2160a7",
- "bytes": 10766
+ "bytes": 10766,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Audit/AuditEventRecord.cs",
"sha256": "5c992ef4d82463c7aa230d9514acf5ef542feabfc7057510beba78eaaf7855e0",
- "bytes": 589
+ "bytes": 589,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/Audit/IActivityEventReader.cs",
+ "sha256": "0b97a6471bd394adbb7df868c9eb6e6e536f909569eaf78d03c11da40c95428e",
+ "bytes": 301,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Audit/IAuditEventReader.cs",
- "sha256": "a5569e83b8ce6304699a40ae661a91f42ac05f4bb06681c9709764d290b729c1",
- "bytes": 394
+ "sha256": "0de8a4df88b5a54573c0ac159bfc4866e3e956af76ae6af4bf15f1c3dc364fdd",
+ "bytes": 535,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Audit/IAuditEventRecorder.cs",
"sha256": "78a58a0357e1cb466dd315e6277d549999657638fb7cc54a36ef4deb307f50bb",
- "bytes": 444
+ "bytes": 444,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Automation/IIdempotencyStore.cs",
"sha256": "0cde1245c250d21bc794a2fb5268ffe722680392b8ff36292ac13447496c2c1e",
- "bytes": 328
+ "bytes": 328,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Automation/IOutboxRepository.cs",
"sha256": "79c6588e2eb89d2cac68f49da398db19dc2689848894c3e9d3c3332b10ff9ded",
- "bytes": 868
+ "bytes": 868,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Automation/IServiceAccountRepository.cs",
"sha256": "e244f7f8c7a6c1e914c22a82b66b853d631fd050be0e91cc8b175753db629ce9",
- "bytes": 1210
+ "bytes": 1210,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Automation/IServiceAccountTokenIssuer.cs",
"sha256": "c7bbdfbadc891447c23470922740cd4a0eef6135139aa11be863b60ee37bd241",
- "bytes": 783
+ "bytes": 783,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Automation/UseCases/AuthenticateServiceTokenUseCase.cs",
"sha256": "f171e19da253eef77b8309866ea4fa6ea02cd3d6dcd5833675360bf89f16ed5e",
- "bytes": 1957
+ "bytes": 1957,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Automation/UseCases/CreateServiceAccountUseCase.cs",
"sha256": "14aa3f4a5b6c8267fa3bf85a1a2d8b51f976f773e69f2b82807aa86a3085264a",
- "bytes": 2086
+ "bytes": 2086,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Automation/UseCases/IssueServiceAccountTokenUseCase.cs",
"sha256": "2e65d12054e16fdb94603141a6816590c9bed8a18bc240b5fc56ea1789fe1ec9",
- "bytes": 2102
+ "bytes": 2102,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Automation/UseCases/ListServiceAccountsUseCase.cs",
"sha256": "7e81c9212e31b1a789862b6a0a2ebed98b75dfaec97b25f12a651ca2ad32a57e",
- "bytes": 847
+ "bytes": 847,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Automation/UseCases/RevokeServiceAccountTokenUseCase.cs",
"sha256": "e9c1e46770cdee378c5c53d15918ff5b6be14979843c582897a1bd9963f953ca",
- "bytes": 1496
+ "bytes": 1496,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Common/OperationResult.cs",
"sha256": "40a9d990d5abf09548777fdebc83a7b7cb051ec0a3cb611b6406248c3cf4b588",
- "bytes": 2157
+ "bytes": 2157,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Common/SafeUrlValidator.cs",
"sha256": "7598bbd83634d0adef18905057e7b7d090a4383d93f8277569be5e73b1704d57",
- "bytes": 1877
+ "bytes": 1877,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Decisions/IDecisionRepository.cs",
"sha256": "31846cb8dc01a99eb6b1e1e365d0e53815c753ddd86ec74bf6df24ca86bc0528",
- "bytes": 1461
+ "bytes": 1461,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Decisions/UseCases/AcceptDecisionUseCase.cs",
"sha256": "380d185bdcb8f75b19357381b00a614af4c6dd8bc8c61fb848c633bf24e62c18",
- "bytes": 2676
+ "bytes": 2676,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Decisions/UseCases/CreateDecisionUseCase.cs",
"sha256": "2af5f9d1ef695fe55b7e868a22e4becb10be9fe5a18d584a01432c631a7314b2",
- "bytes": 4739
+ "bytes": 4739,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Decisions/UseCases/GetDecisionUseCase.cs",
"sha256": "a2b6838be3852fcd569b4ebad889ee1ff19f1cb10aab9125595834fc3da6c310",
- "bytes": 1733
+ "bytes": 1733,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Decisions/UseCases/ListDecisionsUseCase.cs",
"sha256": "d1d010447371772234b4b5614de785c20496f769fba848f0fd64fa45bae501d0",
- "bytes": 408
+ "bytes": 408,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Decisions/UseCases/RejectDecisionUseCase.cs",
"sha256": "6d6ba3ccd487075294d7e6d7e236a36c5494f4e76e8dc8fc0e4b8d1be626825b",
- "bytes": 1823
+ "bytes": 1823,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Decisions/UseCases/WithdrawDecisionUseCase.cs",
"sha256": "053009f9ceed5c3ed37f498c76ad41c3aba8b01e05af29c00e157784621e429a",
- "bytes": 1834
+ "bytes": 1834,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/IInstallationRepository.cs",
"sha256": "4c26debcd4a65fb6b51c0c00546db301ffa987ff480e16c52dcd74ce30ebca0c",
- "bytes": 606
+ "bytes": 606,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/IRecoveryTokenService.cs",
"sha256": "110bdbd3e32d9dcaf621f8052e46c138d8c8802343d1fa5a9398583e2bef9ab0",
- "bytes": 869
+ "bytes": 869,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/IUserAccountManager.cs",
"sha256": "bc5a2df56e72356bcc927e0eaa475f610e02e83d1be761e9a304882b9ee9fbdf",
- "bytes": 2360
+ "bytes": 2360,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/IWorkspaceMembershipRepository.cs",
"sha256": "d79749756802ba301e594004a186b09913e3968379a6b4ec13b8b769d9f8a0a5",
- "bytes": 797
+ "bytes": 797,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/IWorkspaceRepository.cs",
"sha256": "63063de6138b02fe8da2ee91952dabdeb56d20e8b5254a0b9277a1a2dc41463c",
- "bytes": 332
+ "bytes": 332,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/PasswordSignInOutcome.cs",
"sha256": "b9bce7ac37bc066b40442596779dda20dc5a425020600957bfb02e9f977aa6bd",
- "bytes": 754
+ "bytes": 754,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/UseCases/CompleteFirstRunSetupUseCase.cs",
"sha256": "0ada0ffd1ed5d1bba526d838e03700097d93cb61f6e964a8680f9718d92104f6",
- "bytes": 4002
+ "bytes": 4002,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/UseCases/CreateWorkspaceUserUseCase.cs",
"sha256": "67e4ace3828f54705bef1a11e8606260df337eae3d28af5511d0e22246c9f007",
- "bytes": 3926
+ "bytes": 3926,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/UseCases/GetSessionUseCase.cs",
"sha256": "4b37fb46bdc5f4ae882386765c6227078c9ef26fcf1d59614137f0f2ad8bba86",
- "bytes": 1382
+ "bytes": 1382,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/UseCases/IssueRecoveryTokenUseCase.cs",
"sha256": "058c0591cdcae1dab22b94a829df0b4e145bca0875d57f0a3dce7bae8a9c6e30",
- "bytes": 1998
+ "bytes": 1998,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/UseCases/ListWorkspaceUsersUseCase.cs",
"sha256": "9e55c003c5cc3af1fce51873e765ba8c31b321530f0f4ea577e2fc265bb1c435",
- "bytes": 1358
+ "bytes": 1358,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/UseCases/LoginUseCase.cs",
"sha256": "4572268de71f0ccfdd4d163c30dc7dafaeab03ba9cb93f3c33d61d7154881a96",
- "bytes": 2413
+ "bytes": 2413,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/UseCases/LogoutUseCase.cs",
"sha256": "33a815b571dcd78ecac9e7150227efb3b06a110243c644fc631193ecfa469ad1",
- "bytes": 1044
+ "bytes": 1044,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/UseCases/RedeemRecoveryTokenUseCase.cs",
"sha256": "00e1c5844497d977201a8756a92feecfab6d13703268dd921fb954a99b41ed0c",
- "bytes": 2508
+ "bytes": 2508,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/UseCases/SearchWorkspaceMembersUseCase.cs",
"sha256": "3ac25d40f39b1d8cee9cb91327931286e0ef092721604ce0a181b1c28cecf103",
- "bytes": 2378
+ "bytes": 2378,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/UseCases/UpdateWorkspaceUserUseCase.cs",
"sha256": "2318386d4ccdefe2defeab18f74300eba7ab75c3528dfaf3a37852b35feb4113",
- "bytes": 4992
+ "bytes": 4992,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/UserAccount.cs",
"sha256": "32d9da06a848de668d529b30f673fa63b6d11575c0cfc3cf734d5a3f143a3dcd",
- "bytes": 391
+ "bytes": 391,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Identity/UserAccountCreationResult.cs",
"sha256": "16cf1937eeeaad9ba00c103603b53fa01ab0d455067033ca04a31c19c4d3800b",
- "bytes": 777
+ "bytes": 777,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Infrastructure/IInfrastructureRepository.cs",
"sha256": "76a63ad8237342f288d4fa044d1690eee2c28e322bff0896272426a42b19a15a",
- "bytes": 10201
+ "bytes": 10201,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/Infrastructure/InactiveInventorySourceException.cs",
+ "sha256": "af93ed374e1b22791c9bbcd9e8106f95c691bea066ecccebea00565a96a0eb70",
+ "bytes": 286,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/Infrastructure/StaleInventorySnapshotException.cs",
+ "sha256": "5524ade30d863972ffb789343d5b4a8c5115bb09ef852b49996accc29e9c9f8f",
+ "bytes": 243,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Infrastructure/UseCases/ImportCuratedInventoryPackageUseCase.cs",
"sha256": "0ffdb581e76d059119540c4086d02f92b65b4d1f8d46cdaabce5561d9da1277c",
- "bytes": 4729
+ "bytes": 4729,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Infrastructure/UseCases/InfrastructureQueryUseCases.cs",
"sha256": "77cc129af02331fa02d35807e6b02a093d2984eeb746205a4c9c4b72b2ee2479",
- "bytes": 3006
+ "bytes": 3006,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Infrastructure/UseCases/ManageManualOverrideUseCases.cs",
"sha256": "476c9e83c81ff5a3633bea2dfe5279a771b25350c2713cdf191cd96d2c9517ef",
- "bytes": 4275
+ "bytes": 4275,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Infrastructure/UseCases/ReviewInfrastructureServiceUseCase.cs",
"sha256": "d62b15b0e0a23e7ec65b31db2ceaa4f129decb097c836ab71f1ad3dbfb3d82a5",
- "bytes": 3156
+ "bytes": 3156,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Infrastructure/UseCases/SynchronizeInfrastructureInventoryUseCase.cs",
- "sha256": "55812fe5d58a27c07b60099f908c311eee649c74a3d157010c01c7459c5fdbf8",
- "bytes": 2562
+ "sha256": "ea299c2f30ab6d9c40e3b0e93eb8cc0aa8945c3dcca9a39a40180585cdd9f7f2",
+ "bytes": 7154,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Infrastructure/UseCases/UpdateInfrastructureEditorialProfileUseCase.cs",
"sha256": "9c647c6e0968ef15df4dd5c700b97810acc26b8805d431e9ccd58afe6eb9875f",
- "bytes": 4143
+ "bytes": 4143,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Infrastructure/Validation/EditorialProfileValidator.cs",
"sha256": "aa9ed54576f518b99b2480dd1391fe67ac1f649dd1fb6e22dde4246921144e8a",
- "bytes": 6018
+ "bytes": 6018,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Infrastructure/Validation/ManualOverrideCatalog.cs",
"sha256": "8410ac1c9291cd046b5d3696d0bdc15b8f1a30a70e94a49c2e4b985c703e3746",
- "bytes": 9952
+ "bytes": 9952,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Ingestion/IIngestionProposalRepository.cs",
"sha256": "e64d13b80e62517db336eaff31a4fbfd6405af7b1388c5b2b8c44e49ba16446a",
- "bytes": 2043
+ "bytes": 2043,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Ingestion/IIngestionSourceRetentionRepository.cs",
"sha256": "96643e0f3b0fb52dbef38de02be7e83072e4ba88284bbd725e3b61ca617fda45",
- "bytes": 810
+ "bytes": 810,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Ingestion/IngestionMetrics.cs",
"sha256": "965723358264952bde89f5f3bf8c2e681c7f91dcfb1597d21bbf2055316ac171",
- "bytes": 2450
+ "bytes": 2450,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Ingestion/IngestionProposalOptions.cs",
"sha256": "54067a87bfd56997cad12bd8c4c546ed50f1c5457f8081c9bf2a4d437709433b",
- "bytes": 466
+ "bytes": 466,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Ingestion/UseCases/ApplyIngestionProposalAnalysisUseCase.cs",
"sha256": "c73fe271e1577e21a1a9c51280740a26a53387e6280f19b02bfc08d64889c700",
- "bytes": 2265
+ "bytes": 2265,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Ingestion/UseCases/CleanupExpiredIngestionSourcesUseCase.cs",
"sha256": "4c65635361dd334535fff1d9da4988dcd787937550f035d07d7978f1f021d523",
- "bytes": 2950
+ "bytes": 2950,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Ingestion/UseCases/CreateIngestionProposalUseCase.cs",
- "sha256": "dca9aa6442bf72a0fa0edc4a6bb39f05fed72cccc225ce1c62192782762de4e0",
- "bytes": 1922
+ "sha256": "d207f31757ce5e0d8040c1d8d74b1e3ffd2beac9c03c8ed853b7b65dce09c09a",
+ "bytes": 2032,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Ingestion/UseCases/DecideIngestionProposalUseCase.cs",
"sha256": "f41e850b331cd3d7a651d29901c859cb0f8088598317f36831c3460f800203b9",
- "bytes": 2191
+ "bytes": 2191,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Ingestion/UseCases/PatchIngestionProposalUseCase.cs",
"sha256": "dff564af0d629db55a6391a821811ce3f8baa4b17b4f8b76de5df0e5a9a299cf",
- "bytes": 1743
+ "bytes": 1743,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Ingestion/UseCases/PublishIngestionProposalUseCase.cs",
"sha256": "8a4db702b0ec89e892b529d90140d8a420c2676ded8570588ac8640a7ee76fea",
- "bytes": 9843
+ "bytes": 9843,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Ingestion/UseCases/RetryIngestionProposalUseCase.cs",
"sha256": "25a7f13f19dde4cba8acdf979e01bf5364ad8e097704814dfb7bb714e72da38b",
- "bytes": 1619
+ "bytes": 1619,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Ingestion/UseCases/StageIngestionProposalSourceUseCase.cs",
"sha256": "89834979bb9e99e5925f1e00340e7ffa60bb4bc45558ec63e8c4b28a71b7c280",
- "bytes": 6435
+ "bytes": 6435,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Ingestion/UseCases/SubmitManualIngestionProposalUseCase.cs",
"sha256": "407ed475aa9acb2624f13bad2882bba956918ff55bdb87cd97d63d42eae06247",
- "bytes": 2219
+ "bytes": 2219,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/ItWorx.KnowledgeHub.Application.csproj",
"sha256": "6fcf2e217eceaca7f4fe66e4ff9fad4492a20436eeb929fab36981597788aa18",
- "bytes": 447
+ "bytes": 447,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/Jobs/BackgroundJobRepositoryModels.cs",
+ "sha256": "ac6211649bb0c1577be1efc897523873db68d4cdddf42fa497a7af0620b64e0e",
+ "bytes": 691,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/Jobs/IBackgroundJobRepository.cs",
+ "sha256": "596db5a5873b95612085a069577f5e2844c701faa8be73af61b23fb5436d92ac",
+ "bytes": 3140,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/Jobs/UseCases/CancelBackgroundJobUseCase.cs",
+ "sha256": "1737be46ba2c02fbf2a85d79e288efa12029f8d7ec4846e00ba32c1f91926ffb",
+ "bytes": 2594,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/Jobs/UseCases/QueueBackgroundJobUseCase.cs",
+ "sha256": "af2b79090dacbb06e4e3cd01b370a387fed42bc9f90bfb226117c3ff296e267e",
+ "bytes": 5515,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/IDraftRepository.cs",
"sha256": "d5c38dae5d89d09136ca02d1ece1a0493a4ad8592fe5ad420c572abf91f0d098",
- "bytes": 377
+ "bytes": 377,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/IMarkdownRenderer.cs",
"sha256": "d9e331a4f1b280a79d51e68bf9da02908825d3ed5c980025c2fec82f5d0aac8e",
- "bytes": 1084
+ "bytes": 1084,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/IPageCreationSessionRepository.cs",
"sha256": "a82f935e8343e1db94f246c06e6f001b582d30203403bd372a3ca53cc7a78919",
- "bytes": 3106
+ "bytes": 3106,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/IPageRepository.cs",
"sha256": "2fc8d89ac9628d24c1753806f9f747ec2aeb7d45e5ac7efde078f6eb3cf15eb3",
- "bytes": 6486
+ "bytes": 6486,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/ISearchRepository.cs",
"sha256": "095d2892449217568233006abe22d7602b1be3e554bb932fa16b08f3efab2a4f",
- "bytes": 1318
+ "bytes": 1318,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/ITemplateProvider.cs",
"sha256": "1ba878b702d1ea4642396e6e47a44ec29627d5ddaa66c4b87ee23595eda50b6d",
- "bytes": 517
+ "bytes": 517,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/InternalLinkExtractor.cs",
"sha256": "74258e29a94fd1309082996ec1e8d10424f8a88b9d98319a43f59c92e99d3351",
- "bytes": 1661
+ "bytes": 1661,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/LinkResolutionService.cs",
"sha256": "d32c39156f3eda7eaf038c73cf063b0203dfb98f1ee079a56659f72ea3142235",
- "bytes": 2533
+ "bytes": 2533,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/PageCreationSessionOptions.cs",
"sha256": "ea7f80be0cd1674f169e760abc5f7cb177bf63274b7f29a68bc64a17d7892f32",
- "bytes": 1274
+ "bytes": 1274,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/RagPublicationOptions.cs",
"sha256": "c9f125d8493ed26bbea1dc7192e3d8aa70d85f9e95b103efc7fe91a605473e68",
- "bytes": 976
+ "bytes": 976,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/BeginPageCreationSessionUseCase.cs",
"sha256": "8e96da26afb281a9efa053998774d01e1826d59eeb8e61ab0b386ac6c4a6b2f2",
- "bytes": 2033
+ "bytes": 2033,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/CleanupExpiredPageCreationSessionsUseCase.cs",
"sha256": "1cce2f7eda3098b8c0c27c0e1e220805a900232f0a00c5193aefceafc8bece7b",
- "bytes": 5871
+ "bytes": 5871,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/CommitPageCreationSessionUseCase.cs",
"sha256": "287357ace9fb69e7fb7ad00ec4396d8be4a0e91be355c6d7704ef75ca906b20d",
- "bytes": 6728
+ "bytes": 6728,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/CreatePageUseCase.cs",
"sha256": "61ff5e39043ea5c479ff23af297f266f14b80082c5c686298097bf51cd67fc89",
- "bytes": 4904
+ "bytes": 4904,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/DiscardDraftUseCase.cs",
"sha256": "6c8aef55d862add0530606e0dce38d4ff73ff15497edf02f3652732935ceacc1",
- "bytes": 547
+ "bytes": 547,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/DiscardPageCreationSessionUseCase.cs",
"sha256": "dee3bfc35b34b29a2d84178c6a35c1d0410d32f3648e845604c7475b9aaa8c25",
- "bytes": 3554
+ "bytes": 3554,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/GetDraftUseCase.cs",
"sha256": "85cf8209151c0f9557c743cfc05446452ba2c46954f8c189d44445dde1978704",
- "bytes": 374
+ "bytes": 374,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/GetPageBacklinksUseCase.cs",
"sha256": "c4d762c7f642448378a38e97167cc94b259f98c43bf1c9068fe06820cc09f103",
- "bytes": 518
+ "bytes": 518,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/GetPageChildrenUseCase.cs",
"sha256": "5fe86dc39e9230cd1adbf51026a7f33a421c3e599304982837c6d5f876bf37b5",
- "bytes": 1210
+ "bytes": 1210,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/GetPageRevisionUseCase.cs",
"sha256": "eeda2fab0ec8e0fe6b787255b18222c24a3499f4de7b852dc89e1086d2a04dd3",
- "bytes": 1151
+ "bytes": 1151,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/GetPageRevisionsUseCase.cs",
"sha256": "db0765c8b84980553fd4b9de2c811dd62d9349c5cac781ec7a136216d7a545c4",
- "bytes": 520
+ "bytes": 520,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/GetPageUseCase.cs",
"sha256": "9d0366f4f71129de6af0c942bc9efa72ea0531d028ab2de5f2a97db5f4257b56",
- "bytes": 2473
+ "bytes": 2473,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/GetRagPublicationManifestUseCase.cs",
"sha256": "dd3589364fbc315264167e1e9df70119ae82ea6dc4d4e1211cfa3fc09dec1bfb",
- "bytes": 4655
+ "bytes": 4655,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/MovePageUseCase.cs",
"sha256": "2685636fccb94a4337e66f640ab6eaa37b91f12266f757d247748bf94fb9cc21",
- "bytes": 3887
+ "bytes": 3887,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/PageOperationResult.cs",
"sha256": "579fbfaba1f53de86d92d49ebbb3680c9b956609055bc01cc9028fdf8dd62102",
- "bytes": 585
+ "bytes": 585,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/PurgeTrashUseCase.cs",
"sha256": "aabe86a5d7e2e2efee14bd464bd1a5eca48090b5bff55459f7acc20a970d602c",
- "bytes": 1764
+ "bytes": 1764,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/RestorePageRevisionUseCase.cs",
"sha256": "4cefc5d99ea4bf1a1e10605e6db864269cc5f8c5b9ad2c5119e414aebd0826c7",
- "bytes": 3565
+ "bytes": 3565,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/RestorePageUseCase.cs",
"sha256": "f7325338cd0ad757642e2e0bb1a0aa287612b030cb03120b296de7172eace5c7",
- "bytes": 1626
+ "bytes": 1626,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/SaveDraftUseCase.cs",
"sha256": "eabbac408ee7d47a4c56db8adb330e9ab98c67125175efa980af168b7288ac46",
- "bytes": 2651
+ "bytes": 2651,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/SearchPagesUseCase.cs",
- "sha256": "ce2309db46b1e3316c219b9db203644681036013a273bc22d631c0d568bd0912",
- "bytes": 1222
+ "sha256": "8d88b35c356d881d6f98e4d87822e1c68b8440eb0c85bb9a67f868d76af0eb8f",
+ "bytes": 1472,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/StagingCleanupStatusTracker.cs",
"sha256": "acbbb2dc8ebf08d3ac8896238c3e03c515bad9629a8e05637e20bef2e025d771",
- "bytes": 1823
+ "bytes": 1823,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/TrashPageUseCase.cs",
"sha256": "85e74e58ac34c28014b4bae9ee7f848d1fd20f0e6c8078db7ce85d67574db3e0",
- "bytes": 2253
+ "bytes": 2253,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Knowledge/UseCases/UpdatePageUseCase.cs",
"sha256": "02aab6d8f94f1012a1687ff9de0fc77ddc3850b63f74d6d37335721649901aed",
- "bytes": 3837
+ "bytes": 3837,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Migration/IImportRepository.cs",
"sha256": "83b956d65957c4fa2c71eb0f059bc82629f4e32c3c752a3b50ea87cc1eed4bad",
- "bytes": 1226
+ "bytes": 1226,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Migration/ILeafWikiAdapter.cs",
"sha256": "a1835af08a327d02b4f31c1c763b24723badc55f19412159b85e2a68afefcfd0",
- "bytes": 907
+ "bytes": 907,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Migration/ILeafWikiAdapterCatalog.cs",
"sha256": "e34952eab50fb35bdf03c9b42412dc00fcb88819949a051062f4d56f58da5d58",
- "bytes": 738
+ "bytes": 738,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Migration/ILeafWikiSourceReader.cs",
"sha256": "f731e0dbbe01fb698ba66cbc91019caacb0fd10889bf5235bdc01b9c2e2c731e",
- "bytes": 770
+ "bytes": 770,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Migration/IPageRollbackRepository.cs",
"sha256": "90c8b8d348237ae017690079a0b7b356de7c43c1dd6203f03e0dc9c7b27ee0f4",
- "bytes": 1379
+ "bytes": 1379,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Migration/ISourceBatchPurgeRepository.cs",
"sha256": "35645610dd085dcf62b3eed3c525f1aa489bb30841f8d088cbe2451cec1c3ba3",
- "bytes": 1891
+ "bytes": 1891,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Migration/ImportPlan.cs",
"sha256": "c8f28923504b4b37088be90cdf30b9569dd6baf7b192901d8960e2cdc867bcfa",
- "bytes": 1326
+ "bytes": 1326,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Migration/SourceFingerprint.cs",
"sha256": "8427511ae1133a325897e6ed32adbe08343564ffbaeee0e9318eed59a95e32fb",
- "bytes": 1092
+ "bytes": 1092,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Migration/UseCases/ExecuteImportUseCase.cs",
"sha256": "0694273f21367c96db08de251b66e94f4bcc6a5a0d836d69933a789f348310d4",
- "bytes": 16963
+ "bytes": 16963,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Migration/UseCases/GetImportJobUseCase.cs",
"sha256": "cf7b7abc17c7cea7b6133471c54c734d549c40f4b0d3b9cfe91eb961c295fe07",
- "bytes": 328
+ "bytes": 328,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Migration/UseCases/PreflightImportUseCase.cs",
"sha256": "2039c5825aed65800f05cad7551574b7941d50595e369248d05c530271aaffe5",
- "bytes": 3950
+ "bytes": 3950,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Migration/UseCases/PurgeSourceBatchUseCase.cs",
"sha256": "280f07754ce7e95d1e48f8087240a6cd05273fac6f20f543135d960f454d8443",
- "bytes": 3712
+ "bytes": 3712,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Migration/UseCases/RollbackImportUseCase.cs",
"sha256": "db4135809d0987c75ef96080d2f0f0059b102a4244e2cc266a2253741b1b3c44",
- "bytes": 3524
+ "bytes": 3524,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Operations/BackupManifest.cs",
"sha256": "f476252932d2087793fda9732070df1fce6eeb223d0721f0a5be6839b9788339",
- "bytes": 1912
+ "bytes": 1912,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/Operations/IBackupEvidenceStore.cs",
+ "sha256": "235dda6bbdf29db3ce08d98ef2d861f1e72c3afb6404ecad60ae95416507cd3d",
+ "bytes": 875,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Operations/IBackupService.cs",
"sha256": "b2adaca6da7c2ab5eb13f2e896bb4781eb4ef4b8eff201e676f7f7049e5d15c2",
- "bytes": 861
+ "bytes": 861,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/Operations/IOperationalTelemetryReader.cs",
+ "sha256": "5e83d04e3f045579927009dde2515790e776c4f58709d209deba9cfe7193a091",
+ "bytes": 467,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Operations/IRestoreService.cs",
- "sha256": "6f6117c1d9446cd040b2d2ec9972d0cc685a46acc9e0cb24fadcba73764fda64",
- "bytes": 1428
+ "sha256": "86bcb62f33bdbd27b0a5b7b810c45ee9ecda29362eff0e0a750d0b528183d119",
+ "bytes": 1463,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/Operations/PlatformMetrics.cs",
+ "sha256": "37ac545b9ffb9579fc59dbcb5175bb606828c2d4d059af81eee8c5179ad1afca",
+ "bytes": 4578,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Operations/UseCases/CreateBackupUseCase.cs",
- "sha256": "d886a29d1f2cdd1a4d18325b9fb5939ba246134acba1988defaa1603665c6af5",
- "bytes": 2448
+ "sha256": "d8f98372e9462547d917e6a418b985a9235255f2be2ac1c43a9f7ce3fd0a63e8",
+ "bytes": 2599,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Operations/UseCases/ListBackupsUseCase.cs",
"sha256": "1eb784e06252b3972feff812182af9d4522811d687e968797480a7d46e0a609f",
- "bytes": 283
+ "bytes": 283,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/Platform/PlatformSettingsPort.cs",
+ "sha256": "f68a90e389585f06da2cff7dc84f09c27443ebd176227f7ea59b9330c942f861",
+ "bytes": 756,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/Platform/UseCases/PlatformSettingsUseCases.cs",
+ "sha256": "9521112c5284007683390241d1a7a51d849d8cb48af5ace8a1296b0fce46bd79",
+ "bytes": 6030,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/Platform/UseCases/UserPreferencesUseCases.cs",
+ "sha256": "dafff56b2b7603ae5cb382f9ab3376c2b7ad6c813e2c479fc5600815bc39fa0d",
+ "bytes": 6353,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/Platform/UserPreferencesPort.cs",
+ "sha256": "2480e40d3d5445cbf81d83704744054f5e3306489aba6a225824108b63cf1f6b",
+ "bytes": 592,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Projects/IProjectRepository.cs",
"sha256": "3f9d37422e3731da61e322e90e8b4445a3ab8af631c4fed2570101b2e1ad4057",
- "bytes": 3712
+ "bytes": 3712,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Projects/UseCases/ChangeProjectStatusUseCase.cs",
"sha256": "9a0772a2218ac888756db4e91121c8a45b71893c93a61aa3ef6f29dca3c93182",
- "bytes": 2663
+ "bytes": 2663,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Projects/UseCases/CreateProjectMilestoneUseCase.cs",
"sha256": "0c09cc7ef1c26c54eb2aa1ccf4eeaaaea736b7d97d98f0fd1bf25c0091d8e5c3",
- "bytes": 2450
+ "bytes": 2450,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Projects/UseCases/CreateProjectUpdateUseCase.cs",
"sha256": "4e8ce6e9cba700d21a65363a31e5d700ee26b9de4a0f20ecf695d8a14b9af518",
- "bytes": 4478
+ "bytes": 4478,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Projects/UseCases/CreateProjectUseCase.cs",
"sha256": "69e53bc155147aeaa21a4d159bf8ed4b44ec613e9f405acde8e5eb1dc65b00bd",
- "bytes": 2213
+ "bytes": 2213,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Projects/UseCases/GetProjectCatalogTotalsUseCase.cs",
"sha256": "88abc2cdabbd8ab52ac6515be07e5728149643edfbc1c999fd552901156a0b64",
- "bytes": 327
+ "bytes": 327,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Projects/UseCases/GetProjectContextUseCase.cs",
"sha256": "bc905fd81a6cb60fd06e2a9fa3386b8f33e67dce940ca6afbe5b94f7a8566df3",
- "bytes": 5133
+ "bytes": 5133,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Projects/UseCases/GetProjectMilestonesUseCase.cs",
"sha256": "09833d12ba6bd56dc9c57a9e37dcd1e59e2a4d3618a31143b17ac5a2ba3bb2c7",
- "bytes": 372
+ "bytes": 372,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Projects/UseCases/GetProjectOverviewUseCase.cs",
"sha256": "9cea4423d980307ffe13bb8b611570c58589ac0eea9e0945cf769b31e99d0e9a",
- "bytes": 2560
+ "bytes": 2560,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Projects/UseCases/GetProjectUpdatesUseCase.cs",
"sha256": "f51f1b0ddd26fb25e12241b811918beb5a7e112035e77c3a0582258d43d2b398",
- "bytes": 351
+ "bytes": 351,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Projects/UseCases/LinkProjectDocumentUseCase.cs",
"sha256": "e3773e0f32e5b50e8d07edfb2f914b9323658341c863e0bb369efa83c68b7431",
- "bytes": 2401
+ "bytes": 2401,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Projects/UseCases/ListProjectsUseCase.cs",
"sha256": "ebbda87d1e31869b747cc1af865e143ee1f677404087f8e0fefced637f74b344",
- "bytes": 1348
+ "bytes": 1348,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Projects/UseCases/SetProjectIconUseCase.cs",
"sha256": "d2c113d6100d3b743751a45ca988c2805329fc68587cb68a502fdd8093ff0f46",
- "bytes": 7312
+ "bytes": 7312,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Projects/UseCases/UnlinkProjectDocumentUseCase.cs",
"sha256": "51f38dd52346faf46d366c1379380aac687ffdbee9794a805ed5fa7f50330e50",
- "bytes": 1499
+ "bytes": 1499,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Projects/UseCases/UpdateProjectMilestoneUseCase.cs",
"sha256": "782396194553e59f8f1b951e1fa625308148731097f4853dea55cb700e1c7bae",
- "bytes": 2715
+ "bytes": 2715,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Projects/UseCases/UpdateProjectUseCase.cs",
"sha256": "35579002f88c1168bc476ea593245cd6c8688a135c5fb6785014b472ee45c217",
- "bytes": 3852
+ "bytes": 3852,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Runbooks/IRunbookRepository.cs",
"sha256": "ba66358f0e161dd1d95433e02ea1f7df332769e62ab37cb375890af58f922b0f",
- "bytes": 1097
+ "bytes": 1097,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Runbooks/UseCases/CreateRunbookUseCase.cs",
"sha256": "c973e3b96d49a8cae500322dd9555c8921c4c2ba5f15f045c4865a5fc6dfc8b2",
- "bytes": 3671
+ "bytes": 3671,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Runbooks/UseCases/GetRunbookUseCase.cs",
"sha256": "106a91784b742056050472790e2539b7c94885c455c50664a2984528bd140ded",
- "bytes": 1017
+ "bytes": 1017,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Runbooks/UseCases/ListRunbooksUseCase.cs",
"sha256": "576a4f923122935ce1e99909a435145a8bc038fc6fef709a4e3c141168ea691f",
- "bytes": 404
+ "bytes": 404,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Runbooks/UseCases/UpdateRunbookProfileUseCase.cs",
"sha256": "c86fede92f9b9399bb29270cf6c6bedd6e6c01956fe3903f835505d309e2a0c1",
- "bytes": 2800
+ "bytes": 2800,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Application/Runbooks/UseCases/ValidateRunbookUseCase.cs",
"sha256": "e095597b190139c1f01afb412e3bbccbdf57d4d19232e9f641755f8108f9010c",
- "bytes": 2271
+ "bytes": 2271,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/Sources/DataSourceUseCases.cs",
+ "sha256": "85ff0cc3f7d26811e8e35d70d315582166a7e734ebdfa74e5fe60713114c6985",
+ "bytes": 4262,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/Sources/IDataSourceRepository.cs",
+ "sha256": "0cc75b5e2c33f409cbda5e38facaafc28f20997c6ab1b032f64150560259d54b",
+ "bytes": 645,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/UnifiedSearch/IUnifiedSearchRepository.cs",
+ "sha256": "db709d4c92d7df02a10dc1b2365edf478eab44f94c4973041b6a719ab9114e2a",
+ "bytes": 1097,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Application/UnifiedSearch/UnifiedSearchUseCase.cs",
+ "sha256": "91ae52ac21af63762f7120a664b1dc20817c45b6ca823787565b952b3f1d3e78",
+ "bytes": 1130,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Activity/ActivityContracts.cs",
- "sha256": "4bc4ea6c5217e87fe3fb4bf26b84aa87abc0c8234d35d5c6c89bc1c27ee35927",
- "bytes": 343
+ "sha256": "e14f47cf9e84c92f797b85f7323e6720dc5db08500f5c8f3485a80b2cddee1a7",
+ "bytes": 708,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Admin/CreateUserRequest.cs",
"sha256": "1e356619562a6ce95c8e6525f6b484aab6515352864e76f0311a21b860fbc7bc",
- "bytes": 169
+ "bytes": 169,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Admin/IssueRecoveryTokenResponse.cs",
"sha256": "ae7aab2126f1fa2fce016cc1a73a3871b5b1ff2b080a95fc06b1730012dd0c63",
- "bytes": 324
+ "bytes": 324,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Admin/StagingCleanupContracts.cs",
"sha256": "cf36a275ba811632edb9e2af14329f42678349e4a24408bd368e3b584e752110",
- "bytes": 712
+ "bytes": 712,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Admin/UpdateUserRequest.cs",
"sha256": "12c0b7551e3f8c040fc461f2414ce5c804d210d267450cc26f33471eac22bf13",
- "bytes": 301
+ "bytes": 301,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Admin/UserSummaryResponse.cs",
"sha256": "d54783845d869bb03edd751388db17097fb2e1cee09265bc84b42f728eee36d6",
- "bytes": 244
+ "bytes": 244,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/AssemblyMarker.cs",
"sha256": "f4a5b737454da7c8883e71d39daa1cec302805c67c02067589b5a70fb7d29b83",
- "bytes": 81
+ "bytes": 81,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Attachments/AttachmentSummary.cs",
"sha256": "f4eb12211104379392e8c844bd1075e7dd12532e7eff5a5fe518373969519bfb",
- "bytes": 361
+ "bytes": 361,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Auth/CsrfTokenResponse.cs",
"sha256": "c74718c14d73ad6a24bfdf14ea04cb295c3f71ab7363bd8fdaf7ec5b1b19aaa2",
- "bytes": 410
+ "bytes": 410,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Auth/LoginRequest.cs",
"sha256": "d908627bea430ae93cc8ae3ac301186b8ac209b15131e97b00ac0670188f5df4",
- "bytes": 113
+ "bytes": 113,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Auth/RecoveryRedeemRequest.cs",
"sha256": "1e285478dabd029e15ddc1501f43a7ac284b1ad64b6d37f177aac317d3a9d18a",
- "bytes": 125
+ "bytes": 125,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Auth/SessionResponse.cs",
"sha256": "739afe6252275fef45aedaaf40a6fab23670c7ec024811e593c06c6fc21340b0",
- "bytes": 809
+ "bytes": 809,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Automation/ServiceAccountContracts.cs",
"sha256": "c13217dfe25cb7bb62ca4fd3cd6b67ed6279c8e28caeae067678fdc3fa35197d",
- "bytes": 997
+ "bytes": 997,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Decisions/DecisionContracts.cs",
"sha256": "7bf010e3dcafb232403b32bf1a8c2ead0fcd47cfc2ffc1eb4be56da4c73d6c95",
- "bytes": 1021
+ "bytes": 1021,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Errors/ProblemTypes.cs",
"sha256": "1df111e70e84d889c6a88dd393f8b3a31a61c89197a8b6da98478f62f2f6b685",
- "bytes": 3238
+ "bytes": 3238,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Identity/WorkspaceMemberContracts.cs",
"sha256": "85690c34c02fd490b422a6ef36f4716ea91e6ef744471bf6dcda89a1aa59a457",
- "bytes": 640
+ "bytes": 640,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Infrastructure/InfrastructureContracts.cs",
"sha256": "b67a8c57a3da1573bb1e1f723f73b82a0e9ae00d07676b9908e819d191e0d3af",
- "bytes": 11056
+ "bytes": 11056,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Ingestion/IngestionProposalContracts.cs",
"sha256": "ea3963276b4f7c8fb79da9e4f792f0ad610386897ca858d9a2985bf374f1bea8",
- "bytes": 4684
+ "bytes": 4684,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/ItWorx.KnowledgeHub.Contracts.csproj",
"sha256": "5542d279fb7b4b0a15516a8170928a519520f07e48a2b05dd64bc6dde047a2f8",
- "bytes": 210
+ "bytes": 210,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Contracts/Jobs/BackgroundJobContracts.cs",
+ "sha256": "3a2b87f12099b5672023f750627dc9b0bab4a7aa4866ff7d20c83b0f5a45d8b9",
+ "bytes": 905,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/BacklinkEntry.cs",
"sha256": "56662869008a175eeaf6c441795169ec7ebd04915b5436b8fcc1bef82e806266",
- "bytes": 222
+ "bytes": 222,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/BreadcrumbEntry.cs",
"sha256": "611d410c9a47ec2a521de24c8dec02a1a12e187f08d75ef4f6d9b80c5d02aead",
- "bytes": 113
+ "bytes": 113,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/CreatePageRequest.cs",
"sha256": "ac033b1317750b538bef5552fecfebffe0890f30be11a0265aabb40531e3ce3f",
- "bytes": 248
+ "bytes": 248,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/DraftResponse.cs",
"sha256": "c51e6129f22f115d669551b58414a567a52ad8b9c726846b02c68157bf989fd6",
- "bytes": 437
+ "bytes": 437,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/MovePageRequest.cs",
"sha256": "e4c3ebd7e8191a4f825a0e9012ce313df700f74b821999e47545a110a02b0898",
- "bytes": 424
+ "bytes": 424,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/PageChildrenResponse.cs",
"sha256": "746653eb1c04cc12ba44e798d0fc359d8e4b355dd82f1d4e2e0c0b2615762d1c",
- "bytes": 334
+ "bytes": 334,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/PageCreationSessionContracts.cs",
"sha256": "897d00594961f9ce7b9b774e0c205bd881990e80fdb5fea4be88900f9d37f463",
- "bytes": 445
+ "bytes": 445,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/PageDetail.cs",
"sha256": "faec98cc4e610e1135fb06c85795a96e26feebc824f2d9d90c965c8d857ce978",
- "bytes": 572
+ "bytes": 572,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/PageRevisionDetailResponse.cs",
"sha256": "d7dc3c6532a6c3e4b57d5c0574011fbc74aae19fdd372a9d9c73e6da5dfb4fb9",
- "bytes": 322
+ "bytes": 322,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/PageRevisionSummary.cs",
"sha256": "3f00e35bb671fa0971d7c8a748ae6b0bffb47b1681c38d212f2550d8b784bcaf",
- "bytes": 269
+ "bytes": 269,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/PageSummary.cs",
"sha256": "a309477cb676953b85a72b37e13887527ba2bf608bd1e47842b33979868d1573",
- "bytes": 263
+ "bytes": 263,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/PreviewMarkdownContracts.cs",
"sha256": "fc4da9a3ce8c742f65de38e26e8afe2fbb0c4faac3b972592a851d01a16b3467",
- "bytes": 182
+ "bytes": 182,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/RagPublicationManifestResponse.cs",
"sha256": "5346c835998a19c444dfb9d80b537fe4986c8ad3d3c5d7ba17189d4e0e909c9e",
- "bytes": 936
+ "bytes": 936,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/SaveDraftRequest.cs",
"sha256": "352fedb03b728326bb0d22cb2d651f460f62e2af6d74a80f38a25baf473a06d0",
- "bytes": 321
+ "bytes": 321,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/SearchResultItem.cs",
"sha256": "f05fb3b832078aeec40e8f5f87f5f640dc50dfb1a616333c91a87af518eb3843",
- "bytes": 810
+ "bytes": 810,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/TemplateSummary.cs",
"sha256": "3bb262dbfaefb9f1e8f311f0cf60b431a7975f4679a01e745e84fdecc1d8676d",
- "bytes": 156
+ "bytes": 156,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/TrashPurgeResponse.cs",
"sha256": "bba32ede9a5e639fcf48d0e323e0aa671f98bca1bf785932cb44d21e2b1dd757",
- "bytes": 110
+ "bytes": 110,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Knowledge/UpdatePageRequest.cs",
"sha256": "b546926daa3406c6f904f7d9280059e7f8e342d3bd7bfb9aa294e47ad2a99243",
- "bytes": 251
+ "bytes": 251,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Migration/ImportContracts.cs",
"sha256": "839822a46c728e20a3608980c58ddcaeedd3ee80b0bffa0961d4a177459693be",
- "bytes": 1554
+ "bytes": 1554,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Migration/SourceBatchPurgeContracts.cs",
"sha256": "db74e5a27f7b9eb96db75977b1c8fb1c1cb81ac748ce11afdac442fe593f45de",
- "bytes": 1549
+ "bytes": 1549,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Operations/BackupContracts.cs",
- "sha256": "0a16876805be758d255c1281681aa2ab2568991bca0ed70e793f6007cfe43cd8",
- "bytes": 629
+ "sha256": "237c75a2bdacc1a7c5ef796086bf5bdd4c2a5aac7727ae9681f41958ca4b5f91",
+ "bytes": 1512,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Contracts/Platform/PlatformSettingsContracts.cs",
+ "sha256": "fdc59df2bad639a275037b2d371c6eed03829ee92ad8ee6a03857679829b06fa",
+ "bytes": 1554,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Contracts/Platform/UserPreferencesContracts.cs",
+ "sha256": "65387b9ccff983e6072053ce278dcb54dc7c225f97cc6588b7d90835586fbb2a",
+ "bytes": 936,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Projects/ProjectContextResponse.cs",
"sha256": "6477193e420877603bd5b816fdc1848c0816e379836364b139cb6e160ccbc88f",
- "bytes": 1216
+ "bytes": 1216,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Projects/ProjectContracts.cs",
"sha256": "ca89b57a4da134415dff9e9191c06cab536044e26543fd5d6bccece9a2dc9dd1",
- "bytes": 3723
+ "bytes": 3723,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Runbooks/RunbookContracts.cs",
"sha256": "c08ec512028ecb7dcc8509e66c9a5bcfcdda3c7de5509be2ce0b7f90a923306e",
- "bytes": 1227
+ "bytes": 1227,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Setup/CompleteSetupRequest.cs",
"sha256": "0519daa08e5afb46950b1010fdf705021e6a8ee8ced8e4cfd923f63d598ac869",
- "bytes": 181
+ "bytes": 181,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Setup/CompleteSetupResponse.cs",
"sha256": "576c45d58aa08f4945a791e92fd87890a152122487b1234f0a1ece0b8bd1aaf0",
- "bytes": 123
+ "bytes": 123,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/Setup/SetupStatusResponse.cs",
"sha256": "bea9df35265d90e0ef6aa7275d4de2dea74df6cf471f327bb217dde8204b7c6d",
- "bytes": 378
+ "bytes": 378,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Contracts/Sources/DataSourceContracts.cs",
+ "sha256": "80a4da6ec0b6bd7b913317d98caac7ce13ad8ae23d3c8b13e0a76d9a2cc4f931",
+ "bytes": 968,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Contracts/SystemInfo/SystemInfoResponse.cs",
"sha256": "95f2116184cc51e9e4318f3ab08a07573fc19b4ab5f1bf2d010d0b755743a539",
- "bytes": 1312
+ "bytes": 1312,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Contracts/UnifiedSearch/UnifiedSearchContracts.cs",
+ "sha256": "d8865a2fe37301f573baf8e8eadd69b32b53c1f9180fd385889558bb6121580a",
+ "bytes": 811,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/AssemblyMarker.cs",
"sha256": "a05f0f51f88ad56ba31b86cc9ad254115b28ba0c7776b63207b2d041a32793d4",
- "bytes": 78
+ "bytes": 78,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Attachments/AttachmentBlob.cs",
"sha256": "ecefe43f3758398bd29fca7b8f37723f27f753f0ffd4841135f3f8533f3b022d",
- "bytes": 2746
+ "bytes": 2746,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Attachments/AttachmentDeliveryPolicy.cs",
"sha256": "987b84cb6ffed6f57958b124dc7f0a4b81c65a1337716245345efa346cf26054",
- "bytes": 1019
+ "bytes": 1019,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Attachments/AttachmentReference.cs",
"sha256": "5977374a5a431337210a4498af29141a8b9fb750524261ac127c8c9b3b9b77f5",
- "bytes": 2659
+ "bytes": 2659,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Attachments/AttachmentStorageState.cs",
"sha256": "ade532e021f82f04b9e3eb725fd405c42dde1ed4ec0ee2a8bbc15d206e764f8f",
- "bytes": 157
+ "bytes": 157,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Attachments/AttachmentUsage.cs",
"sha256": "5691f6f401e7c5bf897d955dda36d27f9233e6888fe4755774eeff8e5ceb3c5a",
- "bytes": 4405
+ "bytes": 4405,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Attachments/SafeFilename.cs",
"sha256": "e8a0c5afd12f72cc616b60b95d8391a900e3cc1525b2519820ebf4e4b8c98cd1",
- "bytes": 2614
+ "bytes": 2614,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Automation/IdempotencyRecord.cs",
"sha256": "e0927a081094b00f1c8fae7c71cde1a755333c66d861cf38c3e28e153ea7810f",
- "bytes": 1874
+ "bytes": 1874,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Automation/OutboxMessage.cs",
"sha256": "e9291d6cf512d09f20341c215d022cecfef36b9e0cf714ac200694a539208c4d",
- "bytes": 2651
+ "bytes": 2651,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Automation/Scopes.cs",
"sha256": "a5ccffaa44939bb2e3acca675f2d6346f6d442ef2b841954925e6166501f9105",
- "bytes": 2512
+ "bytes": 2512,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Automation/ServiceAccount.cs",
"sha256": "eb751d7ce9ed17247c209eeacc83ce4a2864e6c12e1a65286a96569ac5122f4d",
- "bytes": 1834
+ "bytes": 1834,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Automation/ServiceAccountToken.cs",
"sha256": "e34a7581c0dbe2d15cbb235a3dd15077ff99e249e8437ae7997e3f16bcfa4f9d",
- "bytes": 3278
+ "bytes": 3278,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Decisions/DecisionRecord.cs",
"sha256": "cfe4edd47b28ca717b78d0954b22d867bebf656109e6691552b4c37002d0a46f",
- "bytes": 4633
+ "bytes": 4633,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Decisions/DecisionStatus.cs",
"sha256": "dc1328d16d9a7a8f13a5f87c8f3d94cc89c16786e5140153dca43b26ef438939",
- "bytes": 239
+ "bytes": 239,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Identity/DisplayName.cs",
"sha256": "8550e2ada53afa05f916d12f6e83fcebb6f3b3f0a793f91602926246de76cd74",
- "bytes": 858
+ "bytes": 858,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Identity/EmailAddress.cs",
"sha256": "b342db1a3db0bdf2110d03b30b18e5b36ec536822051d5e9543a72e3bc319542",
- "bytes": 1826
+ "bytes": 1826,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Identity/Installation.cs",
"sha256": "e0d1fa5b4125bd73d95263974a0d1afe892a9d9a7df537d4eb8ab3fc24568d9d",
- "bytes": 2341
+ "bytes": 2341,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Identity/Workspace.cs",
"sha256": "3fcf1385bb5a80a376b3529039c5eb219446c845dbe6bb8ffca846f6095b2174",
- "bytes": 1474
+ "bytes": 1474,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Identity/WorkspaceMembership.cs",
"sha256": "c00334d1e17f1a62e23d19acc0a4191fab2952205a58c9d80fea7ece4e39b463",
- "bytes": 1430
+ "bytes": 1430,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Identity/WorkspaceName.cs",
"sha256": "af534c66b9b76a2cd45cd1c7b276c7d92dbf2d17b4a9a5be3660a74047d0026d",
- "bytes": 2116
+ "bytes": 2116,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Identity/WorkspaceRole.cs",
"sha256": "4a7230d011b27ed6e366889bde4b146c6fb92cd77150df7fdcb25834b040c7ea",
- "bytes": 446
+ "bytes": 446,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Infrastructure/EditorialKnowledge.cs",
"sha256": "47389ad0d4855b4a64582c115eb6ede83fb2ff6aefcc62a14d2a2c3d363e9a3d",
- "bytes": 5828
+ "bytes": 5828,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Infrastructure/FileSystemPathBoundary.cs",
"sha256": "ce8f080a83bb1f3cbb8fc429b2b6691403a54affc7fb0e9656d0653fe44bf1c8",
- "bytes": 770
+ "bytes": 770,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Infrastructure/InfrastructureEntities.cs",
- "sha256": "434ee1885e8d967368d33fe6b5c308b24df613b6581b8efd71dd0095e3f459ba",
- "bytes": 17140
+ "sha256": "9382ebf750547104ff21a41f7f085c62d5ea674fc8a80e7e2df568e3a0ae83d3",
+ "bytes": 17279,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Infrastructure/InfrastructureEnums.cs",
"sha256": "47f813ce30ef5878952c9df1326beba835ca936cf48b7604455063fcd11de0b6",
- "bytes": 546
+ "bytes": 546,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Infrastructure/InventoryChange.cs",
"sha256": "d222ccd7b6bcacd168747226f70646edfd1809182c6a233ca06d1a0d9269ef07",
- "bytes": 1597
+ "bytes": 1597,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Infrastructure/InventorySnapshot.cs",
"sha256": "4914ec4d49146202cf17153227807ec86e4ba05223fe61e045298f30bdcf367f",
- "bytes": 2086
+ "bytes": 2086,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Ingestion/IngestionProposal.cs",
"sha256": "a7e971fa288b598972ed63e7e6fcae511e4022ac84f45228301ad650b25b2eb4",
- "bytes": 18472
+ "bytes": 18472,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Ingestion/IngestionProposalStatus.cs",
"sha256": "52723bdb96db4b61fe608e8470979bdc483762e8b175fd533519f5d20a95c341",
- "bytes": 3075
+ "bytes": 3075,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/ItWorx.KnowledgeHub.Domain.csproj",
"sha256": "c1b5edb028e42d9dc83f9c8a32e440919de008aac3b8c72ea66cf03facfa5101",
- "bytes": 204
+ "bytes": 204,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Domain/Jobs/BackgroundJob.cs",
+ "sha256": "73532892384942d071df5853e65ccb61c17939a1bdee638add6954d8a3957c20",
+ "bytes": 8343,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Domain/Jobs/BackgroundJobItem.cs",
+ "sha256": "2f599140b2c9a91ff3dc23653a57a506f2aa58e60dbc5357d8132b81026943db",
+ "bytes": 1294,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Domain/Jobs/BackgroundJobItemState.cs",
+ "sha256": "0f6383827ddaed4369d41219f44989946f31992bd680953154664dece05068ac",
+ "bytes": 151,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Domain/Jobs/BackgroundJobStatus.cs",
+ "sha256": "340beb1b4ae330ddb4891842d4906cc908183d54bc5499596064c7c70050e5ae",
+ "bytes": 170,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Domain/Jobs/BackgroundJobType.cs",
+ "sha256": "c5e333666627b7714039a6e734cf72a53fb4413786abcb7e92ff0173a2dc78b3",
+ "bytes": 108,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Knowledge/DocumentStatus.cs",
"sha256": "e5303d9d38a684b7de41a8ae270c17d31fa627c9d70d9139dcb16361cf850674",
- "bytes": 169
+ "bytes": 169,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Knowledge/DocumentType.cs",
"sha256": "56a5c151c61ecdb52dab6a17ce152abf89188871c4e2bf81f67bbc969bd94ce5",
- "bytes": 312
+ "bytes": 312,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Knowledge/Draft.cs",
"sha256": "2a1109080f1439b27545e949cd77461703054479060ce2b0fb73d5642ffa94c9",
- "bytes": 4107
+ "bytes": 4107,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Knowledge/Page.cs",
"sha256": "d2f7d5f6dbbeb753af9301362b41b1133dd237632e29d4f0ce6f0b3fc4754b01",
- "bytes": 8197
+ "bytes": 8197,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Knowledge/PageCreationSession.cs",
"sha256": "5026b0d587523d0157a248a503f1f76e6f0c75d3e7913855061e0a2eaac2b9e3",
- "bytes": 4083
+ "bytes": 4083,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Knowledge/PageCreationSessionStatus.cs",
"sha256": "03ff59010c07406bfef7c1b93783fbc7293e88353f1802b8e048768ae5a50e99",
- "bytes": 130
+ "bytes": 130,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Knowledge/PageId.cs",
"sha256": "970f79f6918ad8ebb5ff6cae3850b5b0d1b15495c3c63f56e3ae9711b5de4b70",
- "bytes": 217
+ "bytes": 217,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Knowledge/PageLink.cs",
"sha256": "a6eb37c26feef0bb118b8a6bb97c0c6d5ee00e41cef780ed4f0112548572b2de",
- "bytes": 2059
+ "bytes": 2059,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Knowledge/PageRevision.cs",
"sha256": "f32c1083728aeab7e45440d094530b1b156e3803b954db30edd599c3d1b97e62",
- "bytes": 2337
+ "bytes": 2337,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Knowledge/PageSlug.cs",
"sha256": "b1a9ceebcd83a0c45521c49bf887855187510bc6030d41c4e0e76435734aaf4d",
- "bytes": 1781
+ "bytes": 1781,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Knowledge/PageTitle.cs",
"sha256": "8dced8ff8e2394cbd1723b04753fb4bae8751d2e05d029415b0f12ee5820e4ef",
- "bytes": 847
+ "bytes": 847,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Knowledge/RagPublicationTags.cs",
"sha256": "6cd1f7a8853dcd579944cdbd97849f4796070aace98f273c9c20ddf97b24476f",
- "bytes": 4389
+ "bytes": 4389,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Knowledge/SortKey.cs",
"sha256": "07216e4576150ac96453edce8489eafae9807c4005553a1f24eb5ce32b13359c",
- "bytes": 2283
+ "bytes": 2283,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Migration/ImportItem.cs",
"sha256": "b6a264874c6060c721cbd5d2d85c3fdf9d77e26421e40db4efefd698cdd5e1e5",
- "bytes": 2426
+ "bytes": 2426,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Migration/ImportItemStatus.cs",
"sha256": "67f25fc74696ab90e2fe83b4dd1fefc49fd9212f9bb654dcc18ad2e4525d3327",
- "bytes": 167
+ "bytes": 167,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Migration/ImportJob.cs",
"sha256": "88e87ef0fe96a2e4714d1c18d682bba8ce3198b020546d3cff5f482413047c9e",
- "bytes": 4771
+ "bytes": 4771,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Migration/ImportJobStatus.cs",
"sha256": "ad88031f5a4c809fd64f9064a560bdc229162102f3b5b7e5f7fbd71fb5c56ef7",
- "bytes": 193
+ "bytes": 193,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Migration/ImportProvenance.cs",
"sha256": "463f94642167b95f696a6532c4e179f3923074ebfda103373adda7b2b7c36fd4",
- "bytes": 1545
+ "bytes": 1545,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Migration/ImportSourceKind.cs",
"sha256": "6b9e638c34d51b1415b946f497e4b8662c7717d6ea78bbbf930d227cdb50bfc9",
- "bytes": 154
+ "bytes": 154,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Migration/ImportTargetEntityType.cs",
"sha256": "c36e8ac9d38dda7afc4302bb2cff47ac5354ebfe45b670b193fde6c1825481d2",
- "bytes": 130
+ "bytes": 130,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Domain/Platform/PlatformSettings.cs",
+ "sha256": "438139260e7ede04c0f113f0b507de7e2a8b628644b22948e57844b1e1a599c3",
+ "bytes": 4918,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Domain/Platform/UserPreferences.cs",
+ "sha256": "239a989ce00f42067bb4eb1c92f26f041bace9d6d5d848f113f08625c820033a",
+ "bytes": 3517,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Projects/Project.cs",
"sha256": "03e9e860ce6a9397ca18dd86dc6b62f130e332d5c23a8032ef3548be6f635211",
- "bytes": 9175
+ "bytes": 9175,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Projects/ProjectDocument.cs",
"sha256": "a5145414554707f68ddcd2bf91b3df44ddb3ade631b31beb16453892c0190a54",
- "bytes": 1006
+ "bytes": 1006,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Projects/ProjectDocumentRole.cs",
"sha256": "47bb9f654e0fd5bd2307a9618af47b94a12a4ce3521c5384f94c797d6d2c26ca",
- "bytes": 376
+ "bytes": 376,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Projects/ProjectId.cs",
"sha256": "31235810ecf15eb9ca0740b1204e925543621e436ded4314571dbd921c97441d",
- "bytes": 222
+ "bytes": 222,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Projects/ProjectLifecycleStatus.cs",
"sha256": "8957f929650518ecd5ef2046be900c64279a7004f55e1393afcfaa0f45c3f3f4",
- "bytes": 292
+ "bytes": 292,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Projects/ProjectMilestone.cs",
"sha256": "6671768e30927ccbbebaf38eafae0b88df86f9d852f6415453e5ece7a7a25fa9",
- "bytes": 3003
+ "bytes": 3003,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Projects/ProjectMilestoneState.cs",
"sha256": "42e3cd8ea2ffcb7916c5cdeff2183a26eb283d285e619cc9ddd9754eb401579d",
- "bytes": 160
+ "bytes": 160,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Projects/ProjectPriority.cs",
"sha256": "6f6a4a8f8d9a39450c91e3bf33bbaa7a78a635a6907b3ce254f93c19f8bb3a42",
- "bytes": 140
+ "bytes": 140,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Projects/ProjectStatus.cs",
"sha256": "8d7595bb44c0924012313cc4dc9df304d50d9e994c351c83c88e533956100ecc",
- "bytes": 218
+ "bytes": 218,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Projects/ProjectUpdate.cs",
"sha256": "933822f5954364c5b7e7cd232bd5698f160e637336e25f0c6a7f09924baa814d",
- "bytes": 4819
+ "bytes": 4819,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Projects/ProjectUpdateCategory.cs",
"sha256": "2921d236d8f031297dde42b451d2ffdf7b9ff1b0ff9cb11200d4c980482336ff",
- "bytes": 349
+ "bytes": 349,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Projects/ProjectUpdateVerificationStatus.cs",
"sha256": "420c57ec370dc23bc594fc8f537fa3a4d401ebfd89bce72050556830abfbc1ee",
- "bytes": 280
+ "bytes": 280,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Runbooks/RunbookProfile.cs",
"sha256": "566f89057f31288c3cb81163b5798a71705ecb0b04de4b577d7a07b39e26191b",
- "bytes": 4252
+ "bytes": 4252,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Runbooks/RunbookRiskLevel.cs",
"sha256": "423720b7fa907e67ac45a6abf6dfa9b7fc83c15beda7636797b4d37f5bdb2278",
- "bytes": 141
+ "bytes": 141,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Domain/Runbooks/RunbookStatus.cs",
"sha256": "e44594d3827cf7d4d1f3ed46e5bf4ca8cd1673bfc0900a0a736337b794c5c35b",
- "bytes": 128
+ "bytes": 128,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Domain/Sources/DataSource.cs",
+ "sha256": "e95db09757348ec2b9b89aa3d045516159173e950b536c555fc6a68e50be1163",
+ "bytes": 3922,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/AssemblyMarker.cs",
"sha256": "dec74426a34424ca6e10fcc7affc2849e6120ad25c9b77b896dcb13b9c95b187",
- "bytes": 86
+ "bytes": 86,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Attachments/AttachmentRepository.cs",
"sha256": "bdbcf291789a0883fdbf1c2a8873199f8104089f9c4e33e5b5c11b899d3cc0fd",
- "bytes": 9017
+ "bytes": 9017,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Attachments/ContentSignatureDetector.cs",
"sha256": "6ee1b2111bb1131b6cbc8c29875a1940c235638b52400e75a543a33b0f1e1851",
- "bytes": 7577
+ "bytes": 7577,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Attachments/FileSystemAttachmentBlobStore.cs",
"sha256": "48cefcf9f330754b91a3dd4e08aa7e952cff5b60deb5b9c446bb0b2a6bd07af4",
- "bytes": 5330
+ "bytes": 5330,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Audit/AuditEventReader.cs",
- "sha256": "8b6328d93c07595bb79ff9986ff3890a6ade9d1bfdaa85a12378b3b6400772d3",
- "bytes": 856
+ "sha256": "feaa914dd9cdb2da8c4c3c90aad8eda9d0f774a21c6b1205a3a1dafea50cbfda",
+ "bytes": 2620,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Audit/AuditEventRecorder.cs",
"sha256": "966fb8fb60c8e261176937fe66b6c884c77e0bdc691c1aaa8b6e9e3a1deb91ac",
- "bytes": 2179
+ "bytes": 2179,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Automation/IdempotencyStore.cs",
"sha256": "b3e3dd6e6f6c8c53fcb977721d6d5190fa5e374ae3efc9fc4c835d1052250d20",
- "bytes": 808
+ "bytes": 808,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Automation/OutboxRepository.cs",
"sha256": "9b3b6ae4048ca463e8e961f215a4665d0a6b6b2790e2939ce51b19e23ac12241",
- "bytes": 826
+ "bytes": 826,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Automation/ServiceAccountRepository.cs",
"sha256": "927a4d3eb6def6dbdbd5029e8089623648fb340cdc4b1cd0ad44acd744548f55",
- "bytes": 2306
+ "bytes": 2306,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Automation/ServiceAccountTokenIssuer.cs",
"sha256": "eee6c5691a639633d5c7341cc745dadaee30ac7162cef2a8bd76e9274a312846",
- "bytes": 2918
+ "bytes": 2918,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Decisions/DecisionRepository.cs",
"sha256": "92039bc9ce0e1bab64416243d97eedce439f5751ba3b8b7d3ff93a4b12704153",
- "bytes": 5646
+ "bytes": 5646,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Identity/EnabledAccountConfirmation.cs",
"sha256": "a441d6a4061acc96969f603ceaca9b9acece218526fbbb06ce78429deb000474",
- "bytes": 670
+ "bytes": 670,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Identity/InstallationDefaults.cs",
"sha256": "1e440d241431058efd72216fcad091116b579f282a8c5543ebbccf063be28b80",
- "bytes": 383
+ "bytes": 383,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Identity/InstallationRepository.cs",
"sha256": "2120ba96a9784122b52aaf3d09c4b4cd50aba8e67518da6f6a746253f0be6478",
- "bytes": 2079
+ "bytes": 2079,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Identity/RecoveryTokenService.cs",
"sha256": "7a09fc6f31b4a6e60eb1b74182ea87257987d28490f89072c3c4af22339a8028",
- "bytes": 2095
+ "bytes": 2095,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Identity/UserAccountManager.cs",
"sha256": "de5e9405a6686f5eb9563b9678ad196b1d06b733d8104d3c3c94674e899fdf9c",
- "bytes": 10406
+ "bytes": 10406,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Identity/WorkspaceMembershipRepository.cs",
"sha256": "af615654cfa076009db969ba3293bd5f0c1b5b108b970b61cbfdbf374af7d7ef",
- "bytes": 2278
+ "bytes": 2278,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Identity/WorkspaceRepository.cs",
"sha256": "d2fb2e9500d076ba6a8d2f0c096303e47b270c4d04e54cefead0d6fdf72cc577",
- "bytes": 1219
+ "bytes": 1219,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/InfrastructureKnowledge/InfrastructureRepository.cs",
- "sha256": "14a5ad07630b4a5021b60adbe4f21b2fc1fe0ebc20cfc7d82148aed8a955fc8b",
- "bytes": 46297
+ "sha256": "87a2bced7abd3e2f6b84cc8117e49cbcaff1c87c13febc378187b13c99689102",
+ "bytes": 47647,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/InfrastructureServiceCollectionExtensions.cs",
- "sha256": "6e3152184b8ec84c84554e30d482f8577708bdbf01c897166ae992bb99cfc3be",
- "bytes": 12268
+ "sha256": "64c065ad32602578c419e27bf83a4a44345f5dd91f1506477d1159424446cc04",
+ "bytes": 13733,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Ingestion/IngestionProposalRepository.cs",
"sha256": "ee7db685377749130961f50567e548b46230f7cf44ebaf7e1605205ea53204ff",
- "bytes": 9800
+ "bytes": 9800,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/ItWorx.KnowledgeHub.Infrastructure.csproj",
"sha256": "f43ad4df8cd0ccdd59ab35f4d360a7375ff41b90c09e146648ac2082b300eb98",
- "bytes": 1977
+ "bytes": 1977,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Jobs/BackgroundJobRepository.cs",
+ "sha256": "3f4c5cbab1071659cf572f64e7c6c59c3b9b69f5e2a7d451a095d932d0ad095e",
+ "bytes": 20526,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Knowledge/DraftRepository.cs",
"sha256": "f26a54519b8438f431267290615f4c5af8997319601db0ce4517ee41894871b8",
- "bytes": 972
+ "bytes": 972,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Knowledge/ManagedMarkdownControlMarkers.cs",
"sha256": "46ca709375e7bfbe6d8a05d4b784dc4cb1fcd82cc62265850af7a9f941c28317",
- "bytes": 1993
+ "bytes": 1993,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Knowledge/MarkdownRenderer.cs",
"sha256": "b47b6f266968c087b999971df7d6a6d66158a0ab25b78c767e05b1d09b8dafbf",
- "bytes": 5785
+ "bytes": 5785,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Knowledge/PageCreationSessionRepository.cs",
"sha256": "718fd2fb95f8a0aefe3590a2adf5ba82515669f26b748566e249d439e7e8b774",
- "bytes": 2508
+ "bytes": 2508,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Knowledge/PageRepository.cs",
"sha256": "5adbc6a1bb178d4b6782b8744813448d3b9ed34bd87ec2621a26f794db2ed831",
- "bytes": 14455
+ "bytes": 14455,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Knowledge/SearchRepository.cs",
"sha256": "dead86bcaa367b2a2145527e80985db94876261223584bd66ee5670cd8065cea",
- "bytes": 10486
+ "bytes": 10486,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Knowledge/TemplateProvider.cs",
"sha256": "03d16448b0e6ea94bc181d4c053b27dc2bb9b6d55b89ff8448910a68bbaa91ce",
- "bytes": 3347
+ "bytes": 3347,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/LeafWiki/FileSystemLeafWikiSourceReader.cs",
"sha256": "c662b430a273061149f81e14b74c1dafda25c5b2d11a36798d6272b739c66793",
- "bytes": 9995
+ "bytes": 9995,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/LeafWiki/FilesystemMarkdownAdapter.cs",
"sha256": "2cc4ac904a933ded459ee5a822da7c5c633a064c2fcb9b50b370419dd30896e5",
- "bytes": 11751
+ "bytes": 11751,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/LeafWiki/ImportRepository.cs",
"sha256": "448523af343769e2e995b676f6a4b891cfafb5273dd8876eaa66f37c78e8f32c",
- "bytes": 2572
+ "bytes": 2572,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/LeafWiki/LeafWikiAdapterCatalog.cs",
"sha256": "95669a90fbc68d314885fe92cdbba82c3565666507c4ff27ee7594cca67db62e",
- "bytes": 1033
+ "bytes": 1033,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/LeafWiki/LeafWikiV5FilesystemAdapter.cs",
"sha256": "e6e350cc024a29e0515a711271d8a1fcc98bdd029079397345c21617dffe8acf",
- "bytes": 28263
+ "bytes": 28263,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/LeafWiki/PageRollbackRepository.cs",
"sha256": "96fe7f7961355bd629ab1518e1d5c8994c775cb9cee8f487590be785039f9fb6",
- "bytes": 3146
+ "bytes": 3146,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/LeafWiki/SourceBatchPurgeRepository.cs",
"sha256": "3155060c6ed9dff25439e293ed1aae0a9b1e4e6e5badab7122a58dc880f85d40",
- "bytes": 15355
+ "bytes": 15355,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/LeafWiki/SyntheticSqliteAdapter.cs",
"sha256": "385ba973d2d6e7352c9926f7f0692f3683baa9c56c485b58b129de6d7088791a",
- "bytes": 7007
+ "bytes": 7007,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Operations/BackupPaths.cs",
- "sha256": "06307665479009f869538cd8b02c067603f9d84bd38e7b247ba45e34e11bf9e8",
- "bytes": 343
+ "sha256": "f66f37abe68bd45f548ce3525cbb5e3fd5792971e149b3d818d06d9a701a0469",
+ "bytes": 688,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Operations/BackupStorageSafety.cs",
+ "sha256": "4374075bd9a9d14759caed6933f6480e6ead4dda540862d7b80bd95371dff121",
+ "bytes": 4301,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Operations/DatabaseHealthCheck.cs",
"sha256": "e499b5449be4ffc6b7e21986fd5793d635ae741030b3b2e260b90379999dc088",
- "bytes": 1142
+ "bytes": 1142,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Operations/DatabaseMetricsInterceptor.cs",
+ "sha256": "4f9a186c1e87ec0036f867d938e00582e6aeeeb9a99d6b35debc12114e26bf2a",
+ "bytes": 1986,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Operations/FileBackupEvidenceStore.cs",
+ "sha256": "88d5d97b47db3ea893e9710cd52ba4cb9c80fca21ea04687a729d4a6c661a60a",
+ "bytes": 14506,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Operations/OperationalTelemetryReader.cs",
+ "sha256": "19466618e539962b0ce503042369a4166ef11b078233af003b8b7ce5182291b3",
+ "bytes": 1712,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Operations/PostgresBackupService.cs",
- "sha256": "6bfd16d3e2d9624d88e316624277ab407c14e88ed9e5f2879d9c1ccd14b45551",
- "bytes": 13845
+ "sha256": "e2ce688c1ed527cead2e54960443fee8e18bca099ea0d82ec7b26ac4a3fa7bdd",
+ "bytes": 16839,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Operations/PostgresRestoreService.cs",
- "sha256": "79112a12d80277c4d6fbf3b97ad747ff855d309ac74a8ea0e00c71a8c81aa84c",
- "bytes": 8451
+ "sha256": "fe4a8b96ca8ded3cbb1a26b10a1af5204e9904e005af7d4b33da5ee7f340ad72",
+ "bytes": 24904,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Operations/ReleaseManifest.cs",
"sha256": "a66d471872764249ce540121d7d54a6e0702172c83fb929074e3f879547d2a62",
- "bytes": 2732
+ "bytes": 2732,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Operations/StorageHealthCheck.cs",
"sha256": "37d4178f1d789531b1d4f2f835f2dfff90a4648240592666a3af8b503d57b7c7",
- "bytes": 2146
+ "bytes": 2146,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Operations/TableDiscovery.cs",
- "sha256": "51a30c4605eb0e6b1743136244244d7d3394e0ff27c1eaabdaabffe6b3e05e2d",
- "bytes": 2951
+ "sha256": "116d5f2209adea9be9b4f0e416fb327087f0cf01727745ba2a22545dd419fcb7",
+ "bytes": 4724,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/ApplicationUser.cs",
"sha256": "57b170cc56a1fe90f4c9e37501a8c1425438a67b96f4dfde8bb84247e2bbcdc2",
- "bytes": 1021
+ "bytes": 1021,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/AuditEventEntity.cs",
"sha256": "b042afc922bc2b8000432db5de1f026c459fb058e3b3c78ba29049d8b43bed47",
- "bytes": 561
+ "bytes": 561,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/ApplicationUserConfiguration.cs",
"sha256": "bc17d8afb8769c1ca7370b4179001589a4cf53f056c198674c91589f842619ff",
- "bytes": 555
+ "bytes": 555,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/AttachmentBlobConfiguration.cs",
"sha256": "f1988ee304c77826c3bb7a35d0aa651a076510ce5ac00614336fd1158c98c6a8",
- "bytes": 977
+ "bytes": 977,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/AttachmentReferenceConfiguration.cs",
"sha256": "ab47d614d0ef5ac38902dcf87bd20944bf07de1f364c5ad9d100f25361139f87",
- "bytes": 1322
+ "bytes": 1322,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/AttachmentUsageConfiguration.cs",
"sha256": "e0389efa13e83ab9badc513e8bca45040b53aeb4a301e0aa4cc47ebcbd49f688",
- "bytes": 2519
+ "bytes": 2519,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/AuditEventConfiguration.cs",
"sha256": "0a4a4be8d48fd2797c1eda1a6139f539612ee677a81deef3a5bdf941d353f91e",
- "bytes": 1417
+ "bytes": 1417,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/BackgroundJobConfiguration.cs",
+ "sha256": "5e3313d095a93929dbfa3a6ec154b80f0201b874ae36c7d6113c7afcc6bd5a95",
+ "bytes": 3481,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/DataSourceConfiguration.cs",
+ "sha256": "e17b75d63d0b09cbb101aa281026ff4fde820acca263a26b9f7d70cab1af0a5d",
+ "bytes": 1280,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/DecisionRecordConfiguration.cs",
"sha256": "c42b89b33d71a403bda2a1bb7844d6d009202cd50fd59847aa82b621f6f35983",
- "bytes": 2965
+ "bytes": 2965,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/DraftConfiguration.cs",
"sha256": "a111afe705c1f33797330454707d7b68b88202b9e0b36fd558fedfd37bc59042",
- "bytes": 1568
+ "bytes": 1568,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/EditorialKnowledgeConfigurations.cs",
"sha256": "4e8616e91984d44f3ee1a395cb7dfe44e7bf0506fd6d6fb266712148a64020f3",
- "bytes": 3352
+ "bytes": 3352,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/IdempotencyRecordConfiguration.cs",
"sha256": "df0f4317843b4914024bcc39baae7dfb04edecc7087037f90cb1a6cbc0f08a7d",
- "bytes": 1335
+ "bytes": 1335,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/ImportItemConfiguration.cs",
"sha256": "85ddc9aeaa4042bd19b5980e9537d00b1bb691c5a1250ff47efe5cc178c5db80",
- "bytes": 994
+ "bytes": 994,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/ImportJobConfiguration.cs",
"sha256": "5d51453c46dc48edb0a26d71bff00210b3035d20e09263c0a86bf09c6d60074b",
- "bytes": 1048
+ "bytes": 1048,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/ImportProvenanceConfiguration.cs",
"sha256": "02d19a1b14b8f92e32bbb0472f34713e1d0a57db36c9dc077cfa6132c5292755",
- "bytes": 842
+ "bytes": 842,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/InfrastructureHostConfiguration.cs",
"sha256": "d8ef0e853e5329e992aa1560fe789db801c301c12213a044ee62030e0d55102d",
- "bytes": 1287
+ "bytes": 1287,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/InfrastructureServiceConfiguration.cs",
"sha256": "b9887878c9bf6532589ef559195dfbcd6f2e4cab6087dde383d0a8a59e209d27",
- "bytes": 2412
+ "bytes": 2412,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/IngestionProposalConfiguration.cs",
"sha256": "f8f46a14e05b3d5115d0de16d78dd210f7ffdaa05f9d2b88e4cb44c207f910c0",
- "bytes": 5750
+ "bytes": 5750,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/InstallationConfiguration.cs",
"sha256": "a75e1227e8c2eebd4df8f4872d69a680ec6ffb548e615f2239025e9e357897d8",
- "bytes": 1344
+ "bytes": 1344,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/InventoryChangeConfiguration.cs",
"sha256": "ef9bcca4ffe7f2eea6c657e1d9f4ee849dbf00861858f40f881cddeb86bc2921",
- "bytes": 1172
+ "bytes": 1172,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/InventorySnapshotConfiguration.cs",
"sha256": "ededf6ec0e844622722cb8d28fedd94c7fd8bcebe95e41c0c9126929e3db5a73",
- "bytes": 1155
+ "bytes": 1155,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/OutboxMessageConfiguration.cs",
"sha256": "178b4ebc339a04c0fd17bf2dacc32faf0a7f65588eb598234a25df52883cce0d",
- "bytes": 1834
+ "bytes": 1834,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/PageConfiguration.cs",
"sha256": "1d056c3ff24bc47122f32d34b499bc39c59144ff3c53f525270d924c49250f62",
- "bytes": 3568
+ "bytes": 3568,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/PageCreationSessionConfiguration.cs",
"sha256": "bc1e8dbba8f117d40c30d2e169e214a1f74e90fd0177bfe2fdf3e7ff08de8124",
- "bytes": 1582
+ "bytes": 1582,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/PageLinkConfiguration.cs",
"sha256": "627189435806fd6c614e42d90a3cf109e7fe1d856030397406042fec1ed8a797",
- "bytes": 883
+ "bytes": 883,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/PageRevisionConfiguration.cs",
"sha256": "8c34f8ad4df1aaf8025c6ce555b29416f5efdd4aedb3fc3cfa38e7c7886481a6",
- "bytes": 1434
+ "bytes": 1434,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/PlatformSettingsConfiguration.cs",
+ "sha256": "02614063609fe8d3beddae59e7fa790af66e26383d141365cf25cc4a0a6d433b",
+ "bytes": 2175,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/ProjectConfiguration.cs",
"sha256": "eeacfc9a485dfe616a8e65650339947fc95c519687aaa4ca8b997ff21edb533e",
- "bytes": 3194
+ "bytes": 3194,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/ProjectDocumentConfiguration.cs",
"sha256": "701f12b8a5a5e2c4e66819bb85a4d05bba01b2edc670e94feaf195445ecc1fc6",
- "bytes": 957
+ "bytes": 957,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/ProjectMilestoneConfiguration.cs",
"sha256": "d72e435cd6789339af8e2df1600ad3cafdb2659111427a9e7fd0e32a1bf5448c",
- "bytes": 1717
+ "bytes": 1717,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/ProjectUpdateConfiguration.cs",
"sha256": "257f9614c0f39a5afc22c32a9bbff5bd011cf4f165075c3e00d64ef0b4087a58",
- "bytes": 2204
+ "bytes": 2204,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/RecoveryTokenConfiguration.cs",
"sha256": "5a6aa1649427a14b0016587de43635fdc02f18a7a4bb796f6f25d9b0c812f380",
- "bytes": 771
+ "bytes": 771,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/RunbookProfileConfiguration.cs",
"sha256": "17dfe164b07a80469317ec35770796da5d838f73f865da4f62b44091e547422c",
- "bytes": 2007
+ "bytes": 2007,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/ServiceAccountConfiguration.cs",
"sha256": "05f7566a384f6efc66187098b28337a9a6dea49df36cb2ee2d9df01d821485ed",
- "bytes": 1157
+ "bytes": 1157,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/ServiceAccountTokenConfiguration.cs",
"sha256": "aa4983c755881dae8aaf57ad27fb7a5ea730fd7b89f9af69823f0bd4e96566d9",
- "bytes": 1447
+ "bytes": 1447,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/ServiceFactConfigurations.cs",
"sha256": "72102ccde04a119dc184406db54846de68518273aacd16e8939c0fc56fc4217b",
- "bytes": 6176
+ "bytes": 6176,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/UserPreferencesConfiguration.cs",
+ "sha256": "24834f741ecf0df4d01b1401f5844aa7b98307ad3f46a9ba1351262badd30a44",
+ "bytes": 1819,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/WorkspaceConfiguration.cs",
"sha256": "3e3622cb241a137cbe553aeee321c218cd428ce628a30c0b971144965f35675f",
- "bytes": 853
+ "bytes": 853,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Configurations/WorkspaceMembershipConfiguration.cs",
"sha256": "e330a5532015c1475523aa70754004096228b7812728d633fa926cf112ecbf47",
- "bytes": 878
+ "bytes": 878,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/DatabaseConnectionOptions.cs",
"sha256": "082d951535b6df7a8e8b1d922ea63c310a026bf6c458b859bc36eb9ab6c59cc6",
- "bytes": 371
+ "bytes": 371,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/DatabaseConnectionStringFactory.cs",
"sha256": "ef652423b5f1d8a4235138fb8baf8247194661ec01ffff2bc5d4fc62068275ed",
- "bytes": 1463
+ "bytes": 1463,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/KnowledgeHubDbContext.cs",
- "sha256": "b456897a42d4576917c1ebc6936a5e740c99b63464be2a2d7b7a2c2dc83a4b5f",
- "bytes": 5958
+ "sha256": "d500ba8e1a622a935526ed4810b572a8d6c86a48f77ac1e5597ac5745ac6461f",
+ "bytes": 6357,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/KnowledgeHubDbContextFactory.cs",
- "sha256": "7776afa1cdb9451c39fb52d37168b6472579ef4cfc130841c8691a208f6b5c0d",
- "bytes": 1077
+ "sha256": "48901804e66912e7ddd8b40052798611a53bac9c20b6438c80bb9c2e785d5b2e",
+ "bytes": 1075,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260731162849_InitialCreate.Designer.cs",
"sha256": "46effc45cdea0b166c8dbb8462ba6b5232d770fc70216eaaa09033d053da0d70",
- "bytes": 19665
+ "bytes": 19665,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260731162849_InitialCreate.cs",
"sha256": "a76b3a2bfe8039d55a5818cb9d65e441865a427c4df1d282b36755bbe2aefcc8",
- "bytes": 14084
+ "bytes": 14084,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260731185224_AddKnowledgePages.Designer.cs",
"sha256": "ced1eb0c7275dc843b538e9034a41593d3b242b9ce1ade9d02fd0b5e1ad0c02e",
- "bytes": 31845
+ "bytes": 31845,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260731185224_AddKnowledgePages.cs",
"sha256": "9aab5aed585fe58984584f768384f1860f205ee9a9c088087280e1784f29ad99",
- "bytes": 8884
+ "bytes": 8884,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260731201729_AddDiscoveryAndSearch.Designer.cs",
"sha256": "a352e67918091c2ef60acda11093a55146727e196269d1a1059eab80c0cf6ea8",
- "bytes": 32054
+ "bytes": 32054,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260731201729_AddDiscoveryAndSearch.cs",
"sha256": "e9d73c49bfa24d1888227586aa32926ebef9992577d4094f67c158e5c3fc6b8e",
- "bytes": 4767
+ "bytes": 4767,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260731215422_AddAttachmentsAndImports.Designer.cs",
"sha256": "6e6a1d792e4633ecd308a01e94338d05d2aedbeadbf1bd448916007e82952c93",
- "bytes": 45859
+ "bytes": 45859,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260731215422_AddAttachmentsAndImports.cs",
"sha256": "28e0db6377e9b5a0b5531eb95935dc84fde278e10745bb2a77ad51d4dc5a770b",
- "bytes": 10562
+ "bytes": 10562,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260801013144_AddProjectHubAdrsRunbooks.Designer.cs",
"sha256": "1206414497c45936abe96a6993686391eb41ba9b6a1c735cae649e261b0dcfde",
- "bytes": 65369
+ "bytes": 65369,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260801013144_AddProjectHubAdrsRunbooks.cs",
"sha256": "8b6a267309ec4be99f1b44c37db82e4a4393ee1abbf77a61c2498f21d9e5fa5c",
- "bytes": 14044
+ "bytes": 14044,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260801023652_AddAutomationAndOutbox.Designer.cs",
"sha256": "e766c4626516d705d8904eda31eda68bea1280cfcbfbb6d8c3aa2ee7ae738ca1",
- "bytes": 74306
+ "bytes": 74306,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260801023652_AddAutomationAndOutbox.cs",
"sha256": "02526db84cc8b7ca30a43479470a791cd6e33f94ef55bd01d3f78a0e99cf1a05",
- "bytes": 6832
+ "bytes": 6832,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260802040639_AddStructuredInfrastructureKnowledge.Designer.cs",
"sha256": "cff9c22c72cb7490cb6ca224197a277cee0cdbde2d2ca833d3542958e3d2afa9",
- "bytes": 111371
+ "bytes": 111371,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260802040639_AddStructuredInfrastructureKnowledge.cs",
"sha256": "0e4bb6d2ae5e18407cbfb4b74286dfdd5a833cb83d74f452241c7be6c1b8d787",
- "bytes": 32958
+ "bytes": 32958,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260803193236_AddPageCreationStaging.Designer.cs",
"sha256": "f99e8cdee431be06981b36242da01e6c8ff2011d62962ad656922bfc93f3646d",
- "bytes": 114381
+ "bytes": 114381,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260803193236_AddPageCreationStaging.cs",
"sha256": "5eca746e697518fc9ead6e8d09a06902bf61ce550fb6901d6fa7796dce246387",
- "bytes": 4251
+ "bytes": 4251,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260803195722_ExtendDraftMetadata.Designer.cs",
"sha256": "02b05c4c45aadb2b9c0eb720b6e9776f155ce1f95ff135caa382618002483630",
- "bytes": 115243
+ "bytes": 115243,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260803195722_ExtendDraftMetadata.cs",
"sha256": "b2f9b13872c641de3d58b54be8319af635a568ba03feeb16c30750ef89080825",
- "bytes": 1857
+ "bytes": 1857,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260804005007_AddInfrastructureServicesDetectedNameIndex.Designer.cs",
"sha256": "b94f1d13a804ee35a3286aff631699fc0a78478ea941c647e87fe771ad005f23",
- "bytes": 115460
+ "bytes": 115460,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260804005007_AddInfrastructureServicesDetectedNameIndex.cs",
"sha256": "a8b9c3465d2c3626fb2998bb4439a05db68e2b7773c5a2431fbfc754be4fbb2a",
- "bytes": 914
+ "bytes": 914,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260920120000_AddIngestionProposals.Designer.cs",
"sha256": "7288bc485eb105a5971a8120f282ca2f245e1d3bb6a84eca735fc6f4cf650684",
- "bytes": 126857
+ "bytes": 126857,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260920120000_AddIngestionProposals.cs",
"sha256": "399049a315e5cac2131e964614e42bfc70945de108dbd2a959d03a4dfb2141c1",
- "bytes": 7707
+ "bytes": 7707,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260920155218_AddIngestionProposalAttachmentUsages.Designer.cs",
"sha256": "3e54b8a5e4aaa81bd7f588ec74de3f95cff335a870c8dfdae562d8c963967ad3",
- "bytes": 127635
+ "bytes": 127635,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260920155218_AddIngestionProposalAttachmentUsages.cs",
"sha256": "f145293b34186da861314af8d9d11ecb75fb32e5263618642c9deaead44cf351",
- "bytes": 2618
+ "bytes": 2618,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260920173238_AddIngestionPublication.Designer.cs",
"sha256": "8201e6ef926b33da0511d4814850be3bcfa0e19fd9c369015b0e307e843df3ca",
- "bytes": 131112
+ "bytes": 131112,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260920173238_AddIngestionPublication.cs",
"sha256": "4474288b4b762af30080d27526a9bdf1a952bd2b903c868b01c38cf489967fdc",
- "bytes": 4559
+ "bytes": 4559,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260923221837_AddPageTagsGinIndex.Designer.cs",
"sha256": "1909debeadb1dad39a1c8e4823eef079af2db468032705dc883accc3ab9d39cc",
- "bytes": 131298
+ "bytes": 131298,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260923221837_AddPageTagsGinIndex.cs",
"sha256": "72288097d2c02207e82ed7efbb18ea1565b1d75066faa9ec1b113bd30715c40c",
- "bytes": 784
+ "bytes": 784,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260924182209_AddProjectIcons.Designer.cs",
"sha256": "03035c0b25976008d9f10d560a667bfe01e4aae285e2506ab156eb86a3913f69",
- "bytes": 132691
+ "bytes": 132691,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20260924182209_AddProjectIcons.cs",
"sha256": "29718e07eab4ace7c338b69b31536ca97a9c8c5d01ba49a6078f409b5becf900",
- "bytes": 3832
+ "bytes": 3832,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20261002001948_PlatformFoundations.Designer.cs",
+ "sha256": "1880003749f7f29e42dac56a8ebdafcac66891db792ba891501b190a907ef8ee",
+ "bytes": 146120,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20261002001948_PlatformFoundations.cs",
+ "sha256": "cf3dba546a5af0b71ad4f1379fb5588fec3cb65c5cf6ca28ce546f7a48d1be42",
+ "bytes": 9216,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20261002095318_AddBackgroundJobs.Designer.cs",
+ "sha256": "894b3c7cd75335887c58810ba0d324825e801738b7c740622ae24d7d8cdde86d",
+ "bytes": 156109,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/20261002095318_AddBackgroundJobs.cs",
+ "sha256": "54ffa2a246929bdf07f70cc5524902c3e9eea3e7777bff8cc93945d23256bfd2",
+ "bytes": 7846,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/Migrations/KnowledgeHubDbContextModelSnapshot.cs",
- "sha256": "0eb1a59c19a2ac9afa587c53d46b9f41a0f836925b84756c72d7cbbf3102a786",
- "bytes": 132595
+ "sha256": "0ffc566cf3dfa8733b3b0c0f4124746b064a281a419d10ddbee31310757a60e5",
+ "bytes": 156009,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/RecoveryTokenEntity.cs",
"sha256": "8b191dd8b4d388b8cfcdfd8549cc3c8f3aa515451ebc95afce5e38bdfbe342a1",
- "bytes": 569
+ "bytes": 569,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Persistence/SchemaGuard.cs",
"sha256": "6903b8571b5ee9d6048a74fb3473c20950a8a9754715af113d8890a9085b313f",
- "bytes": 2566
+ "bytes": 2566,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Platform/PlatformSettingsRepository.cs",
+ "sha256": "543d76bbfefacf9a507d12c8b818e09b1e1230301b26533e4ea60661d04b5d60",
+ "bytes": 3569,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Platform/UserPreferencesRepository.cs",
+ "sha256": "5ec4647a9e0a6612434df659d603d2af4304adc1e5a713028ad28a10f953b6c3",
+ "bytes": 1478,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Projects/ProjectRepository.cs",
"sha256": "6b69dde4243a86e437c14145fdd08f102547d4a86f0c5e8c78672840f594b1cc",
- "bytes": 13959
+ "bytes": 13959,
+ "mode": "100644"
},
{
"path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Runbooks/RunbookRepository.cs",
"sha256": "d5b25afe37ce718ace1a370e57eba4880749b7f6f1be8518630a7a93d30643d7",
- "bytes": 5290
+ "bytes": 5290,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/Sources/DataSourceRepository.cs",
+ "sha256": "dea10ea010610de9b6d79b3bc8f804dcf8e516a19e7cd9b9c9a7042b188edf28",
+ "bytes": 2454,
+ "mode": "100644"
+ },
+ {
+ "path": "src/backend/ItWorx.KnowledgeHub.Infrastructure/UnifiedSearch/UnifiedSearchRepository.cs",
+ "sha256": "a415a318c459673a17274f692989e058afb6a80a0137b0ce121d839e8570904d",
+ "bytes": 26978,
+ "mode": "100644"
},
{
"path": "src/frontend/eslint.config.mjs",
"sha256": "f2b1ded6fa9f74260438527be5969e1ae9d708df596ed6bba09e6b9ca51e93e3",
- "bytes": 976
+ "bytes": 976,
+ "mode": "100644"
},
{
"path": "src/frontend/index.html",
"sha256": "01a6f25ed87a6b2ebab9a6ae9a1d2ede2baffb73785930595aaff02fa8f2520a",
- "bytes": 598
+ "bytes": 598,
+ "mode": "100644"
},
{
"path": "src/frontend/package.json",
- "sha256": "ace9c70d5fe5ae6076d4f52494b3fd41fe2c44c4bca518c4233bf6a2a450605c",
- "bytes": 1826
+ "sha256": "8cd83d71ceba97615ab0cfb20dae581d75767b4be13b545da8868165750e4b0d",
+ "bytes": 1867,
+ "mode": "100644"
},
{
"path": "src/frontend/public/favicon.svg",
"sha256": "37fd61466b3a818994e57d56ab8625e7803b94775ccff349b2a05e9fe737c198",
- "bytes": 302
+ "bytes": 302,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/blockpilot-autonomous.png",
"sha256": "427889b0183ad56c4895dcdc3acb9b458b98ad61fa61b388d3f8c4504ebe0496",
- "bytes": 55470
+ "bytes": 55470,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/chimera.png",
"sha256": "7c9eadb384e0b2832e426045dfdbda4f1ed298ae5d3605c8beb90040172eb292",
- "bytes": 353492
+ "bytes": 353492,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/devrunbook.png",
"sha256": "15bccb4ec53777e93ad3c07ac6aa83878898f61f9ae73f63ee415dd936cd029e",
- "bytes": 15050
+ "bytes": 15050,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/dockdeck.png",
"sha256": "b15cc1330d5d79639eca6efe88af796f2448d9bad71eee61fbd339092a673b0a",
- "bytes": 100522
+ "bytes": 100522,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/evidencevault.png",
"sha256": "ff41dabc59bc8c0ce24abe8188b99c1d629a59f971aae41c3e5d9ad116900e20",
- "bytes": 23068
+ "bytes": 23068,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/geointel.png",
"sha256": "3521f712a10796ddaa88c6ad6368997c4c2d30f06324df884c31c22e4b639575",
- "bytes": 25979
+ "bytes": 25979,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/gifted.png",
"sha256": "04856261a6de7124fac925f4d992e9e29042d7a43f0cd74f02f1a6f2e17cb8b5",
- "bytes": 17248
+ "bytes": 17248,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/infrabinder.png",
"sha256": "a29c0c974fa31a7afb234e5fb61ee68c3e000dafdf0ccb1a6ce05e3354d38277",
- "bytes": 13330
+ "bytes": 13330,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/itworx-mcp-hub.png",
"sha256": "10082dc4d75e3fe93a46137b76ef036d95e04af5ea9aaab491f1fffcbca1d1c7",
- "bytes": 1242042
+ "bytes": 1242042,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/knowledge-hub.svg",
"sha256": "37fd61466b3a818994e57d56ab8625e7803b94775ccff349b2a05e9fe737c198",
- "bytes": 302
+ "bytes": 302,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/ludarium.png",
"sha256": "29862374bd95218010a21722fe82e43c469f7180c41d753421000f71843a49ce",
- "bytes": 34655
+ "bytes": 34655,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/lumaops.svg",
"sha256": "b978309cbf85b0fad32793337244805d7451ed330e12b8673ca15007f016f584",
- "bytes": 577
+ "bytes": 577,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/mcop.png",
"sha256": "486a6e8d5a5a3cb840b888cf0e5396701de2b4e5f2cabf9a5c02ba4abc595c26",
- "bytes": 10754
+ "bytes": 10754,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/mobilityops.svg",
"sha256": "3d92feb153ec1cd1b6a523cbcb9b20a1c010eae6da4cd0c4e5cc9a47d4b8b8cd",
- "bytes": 344
+ "bytes": 344,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/pokemon-legacy-rom-builder.png",
"sha256": "967ed6d5806f12d36950a39893ab6580d58cbc334e2b289413b130d53f578e38",
- "bytes": 103981
+ "bytes": 103981,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/portfolio-itworx-tech.png",
"sha256": "8b20df10effac8e8fc404db829275b23b437d54d1c42c57600bce1d66d072d1e",
- "bytes": 54726
+ "bytes": 54726,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/project-launchpad.png",
"sha256": "35bc3d8be877c3cc5d4a702be7c7562cff73fdf3991e421b2758dccf129bc53c",
- "bytes": 7293
+ "bytes": 7293,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/projectbrain.png",
"sha256": "83bcd21e2f2aeb09cb2543d3812a84ad9bd841168cfed7b9b937c25e08231b8e",
- "bytes": 29208
+ "bytes": 29208,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/psforge.png",
"sha256": "82bcafb6f1afa6eef9352febb7106170a63a60b4678c25b1a55b4d95bac94855",
- "bytes": 7379
+ "bytes": 7379,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/quizmonitor.png",
"sha256": "bb6b1b4553fa23ae0c61d7026d946e33237a26595e16ac34ed16093ee9cd486b",
- "bytes": 3091
+ "bytes": 3091,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/ragcore.svg",
"sha256": "3cb8ee1ba0b9223ba4324baac06821de74efc6f0185a884b451738ea1cc45fb8",
- "bytes": 568
+ "bytes": 568,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/spoortracker.png",
"sha256": "240e218c23e97b6ab0aa2a70d3c837d4e88b763ee250ac92765cf0a52183e600",
- "bytes": 4602
+ "bytes": 4602,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/unraid-appops-gateway.png",
"sha256": "1c388d073c49546182cb6ff2587df04601f8e2d32eb9f1038bb1a4a6fca63de8",
- "bytes": 6567
+ "bytes": 6567,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/vacatureradar.svg",
"sha256": "e5fa24a32a3206e44526b1e5e12dabbc3bf0d5968ef4110cb7d451a4b155aa62",
- "bytes": 464
+ "bytes": 464,
+ "mode": "100644"
},
{
"path": "src/frontend/public/project-icons/widefrog.png",
"sha256": "f97ff062faba1acc2d4ad280f00b87a7f7ca54f0e3565d405fda8137aaddae6c",
- "bytes": 8546
+ "bytes": 8546,
+ "mode": "100644"
},
{
"path": "src/frontend/src/App.test.tsx",
"sha256": "6d3682d2a5489a85d802a5111d0ce5918e4479e1f37971a6c6a966af0deb901c",
- "bytes": 4991
+ "bytes": 4991,
+ "mode": "100644"
},
{
"path": "src/frontend/src/App.tsx",
- "sha256": "8b29bb586d679c01fd1b3e8a945cc60078df050dcf81e368dc4c49141131d6f4",
- "bytes": 2789
+ "sha256": "3aa6b596ec0e2f6b93cdaf7cc3c89bf4b54d28a776e1e2a42f0e810e11a9be9c",
+ "bytes": 2918,
+ "mode": "100644"
},
{
"path": "src/frontend/src/api/activityTypes.ts",
"sha256": "807fc7880567fe41cee14eddeb7f1fa839527b13f8dbed7bfb0f7de09a93ca00",
- "bytes": 289
+ "bytes": 289,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/api/adminQueries.ts",
+ "sha256": "13dfd53119de80f04ef07fa505431e98cf23d1162fdbd6b153c5a2e6d185c00d",
+ "bytes": 3979,
+ "mode": "100644"
},
{
"path": "src/frontend/src/api/adminTypes.ts",
- "sha256": "5e1f23d7ae48e94e70f7ce3445dd6804d80873bd927bb562883a9b2b7cef155b",
- "bytes": 1976
+ "sha256": "590f66b7f783e00e4b51770dab90103f40244992d700efa2e3dd9796e34b5b01",
+ "bytes": 2138,
+ "mode": "100644"
},
{
"path": "src/frontend/src/api/attachmentTypes.ts",
"sha256": "028e1f1be74d91d1f9d3c39e024cf5e694a68219ce59f914e0aac9992fdc3afe",
- "bytes": 290
+ "bytes": 290,
+ "mode": "100644"
},
{
"path": "src/frontend/src/api/client.test.ts",
"sha256": "fb8feafa96a8e619f7a79b2b647167169e3bf7e50e2cb6f4d9ca4c9333364ae7",
- "bytes": 2241
+ "bytes": 2241,
+ "mode": "100644"
},
{
"path": "src/frontend/src/api/client.ts",
"sha256": "e6bb3514a6ee97dc7b198994628dcaf281a7b723f4bd0ffc07bff423a23c3768",
- "bytes": 6411
+ "bytes": 6411,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/api/domainQueries.ts",
+ "sha256": "65be533be00600457739cc2936b50f8c934cd8ef85489df376c2ad07f3338bf1",
+ "bytes": 5266,
+ "mode": "100644"
},
{
"path": "src/frontend/src/api/infrastructureTypes.ts",
"sha256": "13fa7ba0d356cd081b59840a7f586b48141ccdee7e18548853ce33c1157e0339",
- "bytes": 5955
+ "bytes": 5955,
+ "mode": "100644"
},
{
"path": "src/frontend/src/api/intake.test.ts",
"sha256": "cac6ce0a0fc9b21f6190b9f19b68a94b7fcdd791b2fc12288765904f8544fde4",
- "bytes": 4765
+ "bytes": 4765,
+ "mode": "100644"
},
{
"path": "src/frontend/src/api/intake.ts",
"sha256": "62ce3e9604296f68aca3515f96ced52fb870e6a59d52134737bc6150561fcd0b",
- "bytes": 4441
+ "bytes": 4441,
+ "mode": "100644"
},
{
"path": "src/frontend/src/api/intakeTypes.ts",
"sha256": "ad1037654fbfacd3116fa7862362ec5fb8031086ea4800aaddb5087bae98147e",
- "bytes": 3656
+ "bytes": 3656,
+ "mode": "100644"
},
{
"path": "src/frontend/src/api/knowledgeTypes.ts",
"sha256": "212d1a540875ce687a25e864966ecd785b6a0a84332529ae7e0bd80b10eedd1c",
- "bytes": 4101
+ "bytes": 4101,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/api/platformTypes.ts",
+ "sha256": "2a35b609dd3de5568c02252e59f4f25b283a59d5e0f8e2fc76ac5107055ce3b5",
+ "bytes": 5249,
+ "mode": "100644"
},
{
"path": "src/frontend/src/api/projectTypes.ts",
"sha256": "fb8dda2261fd54dcedecec55bf8b2898cc82f356ee47736366500aca5e30dea2",
- "bytes": 7785
+ "bytes": 7785,
+ "mode": "100644"
},
{
"path": "src/frontend/src/api/setupTypes.ts",
"sha256": "8a64981191ec496492e45b194829ae2d57c42004c075ea8f9505b0764b3fe38a",
- "bytes": 82
+ "bytes": 82,
+ "mode": "100644"
},
{
"path": "src/frontend/src/api/types.ts",
"sha256": "58be4ccc1003181ba995c13ae1ba30c0f11e6a9fcddc423e9307f2356cc21f71",
- "bytes": 617
+ "bytes": 617,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/api/unifiedSearchTypes.ts",
+ "sha256": "d02fdf1a919d3a9b4b100f377ed1ced1f7c3c1080ef837b731054c7c52b8ccbe",
+ "bytes": 762,
+ "mode": "100644"
},
{
"path": "src/frontend/src/auth/SessionContext.tsx",
"sha256": "bd75bb6e09b78b04aef7a4f621482ec4a3344e2bd29c130dc89b2e27d7742182",
- "bytes": 1467
+ "bytes": 1467,
+ "mode": "100644"
},
{
"path": "src/frontend/src/auth/session-context.ts",
"sha256": "4e242cfebd7d9d9f3c5b8bbd1940fcb29fa6f24b6f3cbd526e654929104617f7",
- "bytes": 617
+ "bytes": 617,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/AuthShell.tsx",
"sha256": "3222e264ae7524802cc4e3cdcb86403c1de591d91439406a68daeb2eb9c6955d",
- "bytes": 1756
+ "bytes": 1756,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/components/CachedDataNotice.tsx",
+ "sha256": "bf955cc45f09bf698fadd0257e0cd82a18cc41ce895dba3a7178055c99b34d82",
+ "bytes": 383,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/CommandPalette.test.tsx",
- "sha256": "728f321a25070b3da081d2f1c4c19c144eefdc23a18a612a0a1497c242e7fa42",
- "bytes": 5663
+ "sha256": "8c8042342f4909342abcacfe8ffebdafea518ea49389f7f159356a5c44bb8aae",
+ "bytes": 5602,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/CommandPalette.tsx",
- "sha256": "22aee35c90ef6006073562b5ae9fe0255d213199ca538d4fd3a2c3158543870f",
- "bytes": 12265
+ "sha256": "cf933deb7b7209ccad0cbedc166df299b87a98bfec0c993e8ab0629ff2f35ea4",
+ "bytes": 12301,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/DataTable.test.tsx",
"sha256": "a98eed66bd060fddd584add5efeebd37003728274b8680c2d90d8ced17274db3",
- "bytes": 7384
+ "bytes": 7384,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/DataTable.tsx",
"sha256": "f92e216bb04fda99b680f5710eabe6acea8dab9c8423f1ec555b8e51327041fd",
- "bytes": 10019
+ "bytes": 10019,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/components/DeploymentEvidence.tsx",
+ "sha256": "20421738abedb7289216b8c93a9772e47bd0bd92b71278190548a8f8323b57c1",
+ "bytes": 576,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/EntityDialogs.test.tsx",
"sha256": "6a33f0c9db1191da1daa93f24d19f0dea1a86084a3b167e31370ced081420ed3",
- "bytes": 5999
+ "bytes": 5999,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/InventoryReviewPanel.test.tsx",
"sha256": "2e6e4b7c6d727d2b0a606ba6a735995de4a29280d8e4622afc4d2bf586865cbb",
- "bytes": 3174
+ "bytes": 3174,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/InventoryReviewPanel.tsx",
"sha256": "d88c29419f5b8bdd662d0759eac9f62ec5c7deb00ff19fc573e676d368457118",
- "bytes": 7844
+ "bytes": 7844,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/LeafWikiImportPanel.test.tsx",
"sha256": "740584be9dc12f063deeae9a54677708f16b898644c6cb66c8f628d487c5c47d",
- "bytes": 3927
+ "bytes": 3927,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/LeafWikiImportPanel.tsx",
"sha256": "a69c67c12a754da08d0db5d48ef80e7b36cfc1b74085a0d2f3ce79c0cab7685f",
- "bytes": 9547
+ "bytes": 9547,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/MarkdownContent.test.tsx",
"sha256": "88280950c55bf2de0e1b96a60d12138b4fa9289acd6da30642825b1934977230",
- "bytes": 3694
+ "bytes": 3694,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/MarkdownContent.tsx",
"sha256": "b44e224cbc05ed2b56a1edd3121e3e22e127f0539f0e498449a7ab82831e7e41",
- "bytes": 2037
+ "bytes": 2037,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/OwnerPicker.test.tsx",
"sha256": "b627f7d665333aec494e34764f3ea195238abce270b6c69b69767613c9177de2",
- "bytes": 3913
+ "bytes": 3913,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/OwnerPicker.tsx",
"sha256": "143d2a2d96d21b46ba41bdacd147a49d21fc3e50b448721ed0ca802ab5b49da5",
- "bytes": 7310
+ "bytes": 7310,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/PageActions.test.tsx",
"sha256": "2aff591fb173a4eb6887bf0640ae2dc131ac513d930ed0cb0d23e2912045f49d",
- "bytes": 3984
+ "bytes": 3984,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/PageActions.tsx",
"sha256": "23dca85a1aa79f6ca19200488af593808dfa32ea3c5d143fa7f2f5578c896c13",
- "bytes": 10032
+ "bytes": 10032,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ProductPrimitives.tsx",
"sha256": "ac8b7aa028c792b649a0df3a6da651aa1818377bfc7746cc3ccbda2a0697feec",
- "bytes": 2090
+ "bytes": 2090,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ProjectDialogs.tsx",
"sha256": "d385c5b17c611b391c11facb9966f7cb0d0d076b23715ce7b4837de96047ad51",
- "bytes": 11824
+ "bytes": 11824,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ProjectWorkspaceActions.test.tsx",
"sha256": "5cc188e83132f23d8d778dc70abc7037024ced268254708b9a8adc1599b90335",
- "bytes": 6183
+ "bytes": 6183,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ProjectWorkspaceActions.tsx",
"sha256": "96af22a2cc25df2dd5e1a7f775a3ae5e302b5530f87fc9684708f17aa62864cd",
- "bytes": 15540
+ "bytes": 15540,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/RunbookCreateDialog.tsx",
"sha256": "06c60b1d14d60cc2638b7ba9480e7af1c25766f0ead85de82b26d1042dea6503",
- "bytes": 4779
+ "bytes": 4779,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ServiceOverrideManager.test.tsx",
"sha256": "6b5596796a4733d85bf726166ecbaca1db92df9e1973db9cb42ec460e8808e47",
- "bytes": 5987
+ "bytes": 5987,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ServiceOverrideManager.tsx",
"sha256": "b3be450753cec5ae662070bf98a1bd2fe87da4c4c33fa2035a922bb509b2af13",
- "bytes": 7269
+ "bytes": 7269,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/Tooltip.test.tsx",
"sha256": "7e45a31916ef6effa4118bccfd7bed3db9a852e8942440cab95421632e571994",
- "bytes": 1784
+ "bytes": 1784,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/Tooltip.tsx",
"sha256": "098b59565771d08f2889adc2f2aee97201bbd1d09b65fd780d1c2e0e38e65fe1",
- "bytes": 1781
+ "bytes": 1781,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/components/deployment-evidence.ts",
+ "sha256": "ccae462b9697ff2ab1fcffb0cd81ef542d21c5cb7907aa8277e1290031c8ae24",
+ "bytes": 3155,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/components/deploymentEvidence.test.ts",
+ "sha256": "671970d0fa010cb41f0e185eac4ba0b99db44350e9d91076bfda77860a0d2991",
+ "bytes": 1627,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/infrastructureFormat.ts",
"sha256": "241bd9adb63e516c94dfcdf6c70686119373c28ae1e1010c4beb50d3c150e360",
- "bytes": 203
+ "bytes": 203,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/EmptyState.tsx",
"sha256": "c917f29ea427db8e1e277bb2e26983900838b07f683c12918cec1cbe75dae3d7",
- "bytes": 351
+ "bytes": 351,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/ErrorState.tsx",
"sha256": "b0fbecc2cb873e9b95d2337131a70b82d20214587d2967e4298ab618f16ff667",
- "bytes": 812
+ "bytes": 812,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/IngestionProgress.tsx",
"sha256": "32fa02e1e6ff61ed931faa9b553503eda2e06034a395e39eec9bf9807a6938c5",
- "bytes": 305
+ "bytes": 305,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/IngestionQueueCard.test.tsx",
"sha256": "11a68093a22e9a174cae284486cf3403841b345b5456ff5a94ed1bf2d72c6788",
- "bytes": 2801
+ "bytes": 2801,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/IngestionQueueCard.tsx",
"sha256": "5aee3f46fc0905e632b2b07a1e701df2ce78b9d8561adca4eacbc69b4977d25b",
- "bytes": 1770
+ "bytes": 1770,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/IngestionQueueFilters.test.tsx",
"sha256": "cbb5167071678c7c9ae5d4ef1a81bd6829726c8490e1e5d7dd23323608d6cc3b",
- "bytes": 1650
+ "bytes": 1650,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/IngestionQueueFilters.tsx",
"sha256": "a3d75b8682675caf5982b78f1079cea30a2f465acf656da0fa2f01197d2231cc",
- "bytes": 4069
+ "bytes": 4069,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/IngestionQueueTable.tsx",
"sha256": "7c76c4b5f8ce417bbdd72dfa2a48267e05730f208140dd86a3678e4835806ff1",
- "bytes": 3185
+ "bytes": 3185,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/LoadingState.tsx",
"sha256": "cd112058bd9a9d8a329550f65d6fd5f28734713db4021fe940379081618e2b12",
- "bytes": 377
+ "bytes": 377,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/ManualDocumentIntake.test.tsx",
"sha256": "7fbfcc3386745a14a56e35688be7d6a54182f357d9e550109bbff7e93fd9575b",
- "bytes": 5532
+ "bytes": 5532,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/ManualDocumentIntake.tsx",
"sha256": "9d85e521d7e16df96fbda9a966065a358ae98b530ff33410484bb7cee56829b8",
- "bytes": 5817
+ "bytes": 5817,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/MobileReviewPanels.tsx",
"sha256": "bc0b380967993edc9eeedd4bbdf645a311c24a94d8c4368ebe8ddd07bf7c7487",
- "bytes": 1501
+ "bytes": 1501,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/ProposalContentDiff.tsx",
"sha256": "0550fa213f6430377152d961d96a85ab7f1ea4b8348099ccae4fbd1020aa54d2",
- "bytes": 1009
+ "bytes": 1009,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/ProposalDecisionPane.tsx",
"sha256": "e2cd6555c96bdd8d005d8ed7be26afc0bd37cb447ee0175b645dc038c503ecb8",
- "bytes": 2296
+ "bytes": 2296,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/ProposalEvidencePane.tsx",
"sha256": "4dc0b3f27cbaf31de0b6ee9ec776d71980afdfc5aa47ce424f839edd57b6f71e",
- "bytes": 1950
+ "bytes": 1950,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/ProposalMetadataForm.tsx",
"sha256": "5103c6d0c51de98bcdaee5e70e730778d71d1f30366ef5940358b74a8f32dd66",
- "bytes": 2897
+ "bytes": 2897,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/ProposalQueuePane.tsx",
"sha256": "5c91a162bf39479bed3c5625447400665c5f8eb6ab70a0dad96eb4c12215744d",
- "bytes": 766
+ "bytes": 766,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/ProposalReviewHistory.tsx",
"sha256": "cda9d69ac4a9c34f833e940405ec54f089efc891f639e5c4d5515e5e00d4047c",
- "bytes": 825
+ "bytes": 825,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/ProposalSourceList.tsx",
"sha256": "ad0b7fd032e523ec6291809a162afe2e03810a2efa3029dcc1c25fbe0beeb6f9",
- "bytes": 759
+ "bytes": 759,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/StatusBadge.test.tsx",
"sha256": "d83abe2cabfce64565861ab20c48a0d2b34d47db24e17dcd15cfb48764d43e73",
- "bytes": 1548
+ "bytes": 1548,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/StatusBadge.tsx",
"sha256": "e410ebf5f9095d2cbca33285d0e9f094d746ebdeaa4228a62bc75a3dbead8970",
- "bytes": 1872
+ "bytes": 1872,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/ingestion/ingestionQueueFilterState.ts",
"sha256": "2df507e0a54c9b7f9848a1975f1c06646073f1669d02a14c407daa64169d33fb",
- "bytes": 2205
+ "bytes": 2205,
+ "mode": "100644"
},
{
"path": "src/frontend/src/components/statusLabels.ts",
"sha256": "9711673b4cf102dc790e12ec1179bebe002f48ffee33efbfb9e8b019e645ddda",
- "bytes": 3679
+ "bytes": 3679,
+ "mode": "100644"
},
{
"path": "src/frontend/src/design-system/auth.css",
"sha256": "6012643644c8dd2d67fbea51a69a61f09ff3fae9a1570d414804a602171a0b78",
- "bytes": 5675
+ "bytes": 5675,
+ "mode": "100644"
},
{
"path": "src/frontend/src/design-system/base.css",
"sha256": "42df86a609b82bafe3c0c1585db94e0bc8736f76091bf9eea645c2a53ed90ee3",
- "bytes": 2261
+ "bytes": 2261,
+ "mode": "100644"
},
{
"path": "src/frontend/src/design-system/components.css",
"sha256": "05513d987cf9dca9bd0a460c0e5063e26d9cf18b35430f4e063d21ac0c64f4fb",
- "bytes": 24623
+ "bytes": 24623,
+ "mode": "100644"
},
{
"path": "src/frontend/src/design-system/documents.css",
"sha256": "6e12961e364e8d117354a9ab4cac7a46162d2d1252848e2eb43abf892ed7672b",
- "bytes": 7042
+ "bytes": 7042,
+ "mode": "100644"
},
{
"path": "src/frontend/src/design-system/editor.css",
"sha256": "7c19d13c29f92be98317067bc9465fb9cabf3ddba4f9004056029147d2b427d0",
- "bytes": 6999
+ "bytes": 6999,
+ "mode": "100644"
},
{
"path": "src/frontend/src/design-system/forms.css",
"sha256": "264238dd76215afeb69e62e0e4949535876a8fdab17ba63445f99b7da753f777",
- "bytes": 9072
+ "bytes": 9072,
+ "mode": "100644"
},
{
"path": "src/frontend/src/design-system/responsive.css",
- "sha256": "229fcc6e8a67078daabb36aac5e3265ca0b06892500e5f7ea901a3ae8e82c4bf",
- "bytes": 8672
+ "sha256": "6531dbfe75470968b56bc9df73d1e6fe987838f256b20ac3936451acfc8a0c79",
+ "bytes": 9291,
+ "mode": "100644"
},
{
"path": "src/frontend/src/design-system/shell.css",
"sha256": "3f6eeab9771ece799ef486bf9c8593070f4f8f20be0abce9c924f006195b0a93",
- "bytes": 10347
+ "bytes": 10347,
+ "mode": "100644"
},
{
"path": "src/frontend/src/design-system/tokens.css",
"sha256": "3aa66166f06719569ec3906dd52495075adef217c1942b65402c9f18b936cf8b",
- "bytes": 5566
+ "bytes": 5566,
+ "mode": "100644"
},
{
"path": "src/frontend/src/design-system/typography.test.ts",
"sha256": "e683614e7a558a6cebbf73a01588bfd0d2595f367ce9f117c93b0995a5714a53",
- "bytes": 6757
+ "bytes": 6757,
+ "mode": "100644"
},
{
"path": "src/frontend/src/design-system/workspaces.css",
- "sha256": "410521fdbfe7ab23425f988055096845f7e396a759d9493b1ce9fd0dfcc6d0a4",
- "bytes": 44803
+ "sha256": "9f3274f209abbce164bfeef99fcce3adfd52b560525989a53652dee280354395",
+ "bytes": 48585,
+ "mode": "100644"
},
{
"path": "src/frontend/src/hooks/useDocumentTitle.ts",
"sha256": "5534a1472a981c98c3408fe53cf97ceb2d76b5bebb74129472859345b565f847",
- "bytes": 266
+ "bytes": 266,
+ "mode": "100644"
},
{
"path": "src/frontend/src/hooks/useMobileViewport.ts",
"sha256": "54f806bac73c6cfd476bebf94e18887361945d0336494496a120c4bc5d42951c",
- "bytes": 2223
+ "bytes": 2223,
+ "mode": "100644"
},
{
"path": "src/frontend/src/hooks/useOnlineStatus.ts",
"sha256": "97eb14b3e9cc86cb963e7a2b64853034352a96bfbd33e8bb19a7664759265e72",
- "bytes": 1303
+ "bytes": 1303,
+ "mode": "100644"
},
{
"path": "src/frontend/src/layouts/AppShell.accessibility.test.tsx",
"sha256": "1a384b988564c29da990b1fbe3bf807d7eb0095f8069d21d73c915fb5e36a20d",
- "bytes": 2544
+ "bytes": 2544,
+ "mode": "100644"
},
{
"path": "src/frontend/src/layouts/AppShell.tsx",
- "sha256": "06bda7a6984a8e88a30d8d5f433d204c275a9af55c68b00c4b8db8b0a9533dd6",
- "bytes": 25060
+ "sha256": "16fdfe23d9af0fc7e97455a1b42b3d9dbf3aa6522abb32941fd9a8ae99cc1bbc",
+ "bytes": 24684,
+ "mode": "100644"
},
{
"path": "src/frontend/src/layouts/active-entity-context.ts",
"sha256": "6473ee840c649799c8154d2b8905e2ae05862c4fc4da945d621dcd7fa6cf2527",
- "bytes": 2152
+ "bytes": 2152,
+ "mode": "100644"
},
{
"path": "src/frontend/src/layouts/context-shortcuts.test.ts",
"sha256": "9e989f1b92093062d9477a4c7e2159f6f630f7c9d0d5ab088f1a97d4f4a1ba9e",
- "bytes": 1636
+ "bytes": 1636,
+ "mode": "100644"
},
{
"path": "src/frontend/src/layouts/context-shortcuts.ts",
"sha256": "97c42c2b64bb077e569abfc059cd2f805f82593197ace022e3094150ae640e2d",
- "bytes": 903
+ "bytes": 903,
+ "mode": "100644"
},
{
"path": "src/frontend/src/lib/clipboard.ts",
"sha256": "77c402ff2e4534f29128088e558329c1cf3036a3b0f2a96677a6c3bc70188653",
- "bytes": 1977
+ "bytes": 1977,
+ "mode": "100644"
},
{
"path": "src/frontend/src/lib/uuid.test.ts",
"sha256": "aea10ca1598b963b6240675a7aa3a71618109eefc5f324cd0d5c6dcd2280e5b8",
- "bytes": 1334
+ "bytes": 1334,
+ "mode": "100644"
},
{
"path": "src/frontend/src/lib/uuid.ts",
"sha256": "542adb8514eaa15bfdcc257a1609a68fdefd94c8b005246397e0104846ecd9cb",
- "bytes": 2106
+ "bytes": 2106,
+ "mode": "100644"
},
{
"path": "src/frontend/src/main.tsx",
- "sha256": "a5323c4abcc96b43a2a1e83bb3988f84be503075a0d90a7daf46f083b79807f9",
- "bytes": 925
+ "sha256": "4f20f3838b5baf770522d9d13919a47a216f8b9751a662704fe3387b2a825b23",
+ "bytes": 1667,
+ "mode": "100644"
},
{
"path": "src/frontend/src/notifications/ErrorBoundary.tsx",
"sha256": "193ccfccca3d3dd18db64d309ad0a2f13956a5fa0d1e5d5b99ad03bd64e39d9f",
- "bytes": 3274
+ "bytes": 3274,
+ "mode": "100644"
},
{
"path": "src/frontend/src/notifications/OfflineBanner.test.tsx",
"sha256": "324dc3e70ccb0342335d8b1e44271e910b21317163b89e4c45c950c508c8fe13",
- "bytes": 1898
+ "bytes": 1898,
+ "mode": "100644"
},
{
"path": "src/frontend/src/notifications/OfflineBanner.tsx",
"sha256": "0c16dabe2000c8d775ed8b838768a6a6b02ab322e07529ea118a56e1290475b7",
- "bytes": 1287
+ "bytes": 1287,
+ "mode": "100644"
},
{
"path": "src/frontend/src/notifications/ToastProvider.tsx",
"sha256": "6ed0217d7c21867ba7f2b278a9de2c7c8494b23f1d08fdf8923874dc14d5bc70",
- "bytes": 3622
+ "bytes": 3622,
+ "mode": "100644"
},
{
"path": "src/frontend/src/notifications/toast-context.ts",
"sha256": "b4fbe6217feab3d02e3a49b45b5e6d5383ce8600e432202deedbaf659ec7e1b7",
- "bytes": 2542
+ "bytes": 2542,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/AdminPage.courseTagging.test.tsx",
"sha256": "1fd3d7478bda0f4d7dbaa7a26ba6320265235d43488e426123c58231bf8dd01c",
- "bytes": 11619
+ "bytes": 11619,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/AdminPage.test.tsx",
"sha256": "fdbce01e913a1faf3af6a2cd0728e4846a20f6bea302bccfd2409b7a9de8192b",
- "bytes": 15685
+ "bytes": 15685,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/AdminPage.tsx",
"sha256": "c401bd59c461cc4d9c6771afcd115a2982321a257155800139251368d810992a",
- "bytes": 38145
+ "bytes": 38145,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/LoginPage.test.tsx",
"sha256": "0735439984705d1b1e91c40a56bfef67b6161a7050dca5a24c6c2f419acea2fd",
- "bytes": 7510
+ "bytes": 7510,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/LoginPage.tsx",
"sha256": "e31525d48251bb4b21ab50ecfe941440e52a1b58fd019b61a7ebd219b03cb2be",
- "bytes": 10718
+ "bytes": 10718,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/SetupPage.test.tsx",
"sha256": "58684772100475407c40e9450d6aee7484f146148feaf77ff260f7c3c7a10546",
- "bytes": 1611
+ "bytes": 1611,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/SetupPage.tsx",
"sha256": "370e52b926012a516e2413bf5b48ba292ed6d496796ec5ec03cc25728d4a75c8",
- "bytes": 4681
+ "bytes": 4681,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/StatusScreens.tsx",
"sha256": "be2ca9e5abf4c90aaff36c76a520b682b6966e73e44bfb1258fcf9fb63c18bc1",
- "bytes": 535
+ "bytes": 535,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminAccessPage.tsx",
+ "sha256": "fe29275afb2f37d1a5ae20142adea7eacbdb0305d434b78359e72abedf260fbd",
+ "bytes": 8312,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminAuditPage.test.tsx",
+ "sha256": "654aeea03b1bf9c115dee34a0fb1bddc3d94e439f51c4e3a44ccf4b27c620816",
+ "bytes": 2760,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminAuditPage.tsx",
+ "sha256": "9f3d8e389e5ea3500eed5a044817825aeb628de18017d203e517d05278f0c905",
+ "bytes": 5449,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminBackupsPage.test.tsx",
+ "sha256": "ba97ee5af2a1c0ad48bbef9b631c5ea911dd085134347e3fa819d5e51b28e766",
+ "bytes": 3931,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminBackupsPage.tsx",
+ "sha256": "6d0b3dc80e1bb073a2e8685ba447fc6200b1867fb162e90e0e08976dbc6db351",
+ "bytes": 10448,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminIntegrationsPage.test.tsx",
+ "sha256": "6d09b4af8d2e7c4fc25b28dc960ebf14d6ca83810e554757a52e746058ce08a7",
+ "bytes": 5646,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminIntegrationsPage.tsx",
+ "sha256": "46764d4fb4f52e5eb31ff13dacb896887287693c9409bf9d2dc505ebc3e5d8bf",
+ "bytes": 12874,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminInventoryPage.tsx",
+ "sha256": "b6bd6fa9ff2bf4d79015fad6f6c8a33d1159ab316e1166e82732b8ebc43182cd",
+ "bytes": 3924,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminJobsPage.test.tsx",
+ "sha256": "18a737dacb9a927c38dbb2cb5f81881c43fa4c9193d13e380720a206f651463a",
+ "bytes": 9314,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminJobsPage.tsx",
+ "sha256": "6637f59491551c2f22a16636b1e94056bb67ccabd7c5ff524e11dc4cf757b251",
+ "bytes": 12508,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminKnowledgePage.tsx",
+ "sha256": "1152ee4191bcde19bf25e31f2be021a62719d0aea6e598d1962716410a4284c0",
+ "bytes": 686,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminOverviewPage.tsx",
+ "sha256": "908f9b030be214008256d4f4372f10c261b9acddefa00d2ef6ae8643c7956600",
+ "bytes": 7273,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminPlatformPage.tsx",
+ "sha256": "c287d14a9fe432c2b2b77e44794bcfd836c7e2a20fcbb0229eb6216e81f654ec",
+ "bytes": 11110,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminSystemPage.test.tsx",
+ "sha256": "3e27523dafde4b580f5fff5f12014d1d707a50949ba2da60c5833bff647e6592",
+ "bytes": 3680,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminSystemPage.tsx",
+ "sha256": "70d53a98f3c216c71c6b62566b8b91a029d342dce74c8e47168c91e7f5a2502c",
+ "bytes": 7586,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminTaxonomiesPage.test.tsx",
+ "sha256": "bc5cd845472adca29d64db2121ae3e481dcff4cf2b029c0704d47517284c06d8",
+ "bytes": 2452,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminTaxonomiesPage.tsx",
+ "sha256": "627b60002990b86a144583407b30ec4fd5d5345e54f704655ed016ca973bd3fe",
+ "bytes": 4603,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminUsersPage.tsx",
+ "sha256": "53773d4a212865aa95e6bdc05d25af17865cfd45e3ba3dbf49fec15b8bd06062",
+ "bytes": 7754,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/AdminWorkspaceLayout.tsx",
+ "sha256": "7e0e6798eae46f32f46c1e348a376d24d467beb59a3af46dfc67b1f6a3237d31",
+ "bytes": 1176,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/admin/CourseTaggingJobForm.tsx",
+ "sha256": "87a1565733b70ea5899bd05649e08bea34eda2229e97d6b95ba1826119f79d44",
+ "bytes": 4074,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/Attachments.test.tsx",
"sha256": "41f060d518de44e15322c6d25cf81ff4e9ad7cb420bcc8613d6a0c786320824b",
- "bytes": 4158
+ "bytes": 4158,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/Attachments.tsx",
"sha256": "934ea510c84d1ad0a56c136eb95aefebc5ab0d592368e9780aaaf963d7168194",
- "bytes": 4605
+ "bytes": 4605,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/Backlinks.test.tsx",
"sha256": "fc6119da886624491d0ee025ec60dcebc19554ecaa8ec51b276fcd23d24043c9",
- "bytes": 1970
+ "bytes": 1970,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/Backlinks.tsx",
"sha256": "b52a36b30790a00b541bc57026e0a54b42474e2550829b795de5e1af61a73fd6",
- "bytes": 1887
+ "bytes": 1887,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/KnowledgeTree.test.tsx",
"sha256": "393ca8197144c518c4b192b9af05e2313300e12c1c508f3d673a9ca4916546e7",
- "bytes": 12505
+ "bytes": 12505,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/KnowledgeTree.tsx",
"sha256": "669cc8f5ec3dbb6058a1f121b0de55a9bf51d390151807500cae6430a9033bdf",
- "bytes": 16468
+ "bytes": 16468,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/PageEditorPage.insecureContext.test.tsx",
"sha256": "fcd6c673d5c323756a63c495eb965a4f4703053a1db8abd9c47fc4032832e2ab",
- "bytes": 2441
+ "bytes": 2441,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/PageEditorPage.test.tsx",
- "sha256": "fc4013443bd4312fb2ad01c21b6a127e60a85baed40860f97fe296391271e80e",
- "bytes": 23744
+ "sha256": "6a426e4906772be6ccf9ed027eac825569b92247b10ce29b1fde5832b2b6993c",
+ "bytes": 27316,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/PageEditorPage.tsx",
- "sha256": "ad31836a900aa7c9b149fd675853d77d91e050c1d3f8955709a563b89703f636",
- "bytes": 31286
+ "sha256": "620ad79022c59c8cd5e13b8d93ff5d4d5c3093f58892bf31fbd8c99014f0392f",
+ "bytes": 32643,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/PageViewPage.test.tsx",
"sha256": "0c67d1d09d4e641ff18ee92e2753598c5eee2d3cb46686e6469addfe6fff3069",
- "bytes": 6567
+ "bytes": 6567,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/PageViewPage.tsx",
"sha256": "65625b32065bccea4057e351f2e9d6ce9b877b25f49fd38f724159ca514ae350",
- "bytes": 9717
+ "bytes": 9717,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/RevisionHistory.test.tsx",
"sha256": "5d6b17aa5e1ac7a45bbfaf2f04648628abe58228626a94b196510461de95466d",
- "bytes": 3850
+ "bytes": 3850,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/RevisionHistory.tsx",
"sha256": "7f93dc9acebc576ef42264c65f1147683983c469891b5999695bea8dfebf69d1",
- "bytes": 7536
+ "bytes": 7536,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/SearchPage.test.tsx",
"sha256": "a08aa1bbed51daac290eab427f35727d77383ef3942da2bbeba0a7818038d2f3",
- "bytes": 7571
+ "bytes": 7571,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/SearchPage.tsx",
"sha256": "20926e56e1cd7fdd619dcd79b681a29783b4ce9f5a7b09436432ea8ee0cedea9",
- "bytes": 7207
+ "bytes": 7207,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/editorAppearance.ts",
"sha256": "a6fe89a104c018d5ef61160201bb9c4ce963ae7668120e4954750ae44d23ff5d",
- "bytes": 5922
+ "bytes": 5922,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/knowledgeMetadata.test.ts",
"sha256": "06ba8598a6dc76c48f9de131c476d860b643ffa2205d95e1d02eafccbbabd40a",
- "bytes": 943
+ "bytes": 943,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/knowledgeMetadata.ts",
"sha256": "fa54f6da13302e2864741fc53f16506139349f82b333cf33bd88e7085166fff2",
- "bytes": 1653
+ "bytes": 1653,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/recentKnowledge.test.ts",
"sha256": "242769a93daae7022bcac98adf30d4ba57a1750b94f787a03673a052b838896b",
- "bytes": 972
+ "bytes": 972,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/recentKnowledge.ts",
"sha256": "972d03910c80f9b5eeaf9721665061ba60ec38f46ad989821430c532bcc3cf99",
- "bytes": 1095
+ "bytes": 1095,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/knowledge/revisionDiff.ts",
"sha256": "3b3a7b54cdde2c7c9cab6bfbb34c36e66720f9031d9612e25d4fdc259bb5522a",
- "bytes": 941
+ "bytes": 941,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/projects/DecisionDetailView.tsx",
- "sha256": "6a7d2dc4fd079f547ca3240d477aef8247e44bfec6138d9f3a9a174d1f0714b4",
- "bytes": 9339
+ "sha256": "45ebc7cbf71f047201c5c793f72bfbb0cac4e0a2e53437ef676cc43b33eaf28c",
+ "bytes": 9722,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/projects/RunbookDetailView.test.tsx",
"sha256": "c82229b148f7557c2ceb3e1b290465bb42147aef3e43f96795c24abddf6f9c08",
- "bytes": 3529
+ "bytes": 3529,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/projects/RunbookDetailView.tsx",
"sha256": "b152ab77859438ee2093f482b606dbd4df42cf6c88b3603d3c964e4e758d4f2c",
- "bytes": 10943
+ "bytes": 10943,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/projects/formHelpers.ts",
"sha256": "50f1bc388997fc5696f8076e1b6fa756fdc39e9def166b4f12b47e5bfcb2c592",
- "bytes": 464
+ "bytes": 464,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/ActivityPage.test.tsx",
- "sha256": "e831b5392ccfe398e9fac6f834f128b7104ab1c5597d2ba0003980b7016e1b5c",
- "bytes": 2017
+ "sha256": "addf0ab0f2353684bab226348c527933bd9584b38fc2e2e75e1f0c0b85840d08",
+ "bytes": 2107,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/ActivityPage.tsx",
- "sha256": "9de80f11748165a4366d260842b1f10e94741086cfdb90fa3dd965d5e3a8a6c7",
- "bytes": 5491
+ "sha256": "e6c97582cbd43bbc9e73fac62c867e687413c9c59aa4bffbfccae07858e947c3",
+ "bytes": 4951,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/AdminRoutePage.tsx",
"sha256": "eed028b2549cb07d6e3bf24fecc433bb34614accdf7edae8f0e4de77ad676039",
- "bytes": 202
+ "bytes": 202,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/DashboardPage.test.tsx",
"sha256": "e5f9c7de670616a02598f89e475c022a94b8ea2d58190a77082ee28561e04176",
- "bytes": 9522
+ "bytes": 9522,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/DashboardPage.tsx",
"sha256": "dbcb0cdab67c76d2049c3b4c4d2b9ffc6e1a8115ea92c150164d857b4c1418f8",
- "bytes": 11706
+ "bytes": 11706,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/DecisionWorkspacePage.tsx",
"sha256": "aa0a3085bda7b9126c863e23c339d537903b23ca451e79789979bd3ac851b9c9",
- "bytes": 1199
+ "bytes": 1199,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/InfrastructureOverviewPage.test.tsx",
- "sha256": "ee6f69ada39849a55ffff66a8810e81e3aef34cb3104346175b14bd54fe0f265",
- "bytes": 3110
+ "sha256": "7fb1a7a69f679bde28a9a15a98347974c58851bffa6d2dddf1a0583334a7dc46",
+ "bytes": 3231,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/InfrastructureOverviewPage.tsx",
- "sha256": "81d5bd6d68ce719e61db421941d2c3c92a8691c88bfcd6d3dc733a414929ff2f",
- "bytes": 5289
+ "sha256": "e7313de4cca4b39fd31e2691b0b8337e6fe9b16883264c321253cf811bc649ab",
+ "bytes": 4709,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/IngestionQueuePage.test.tsx",
"sha256": "1235119a2619473f50d8c3f388fb7e04976794ddab5c6c556d9a02f73c875966",
- "bytes": 14414
+ "bytes": 14414,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/IngestionQueuePage.tsx",
"sha256": "c2256a0f9d4f935884dc98ccca91e0778c7564070ae4f62702c210ec94eb88f3",
- "bytes": 6873
+ "bytes": 6873,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/IngestionReviewPage.test.tsx",
"sha256": "495c6094bb36d99767864bd0c361982be0534fba08146edd5e9fea791959adb8",
- "bytes": 25611
+ "bytes": 25611,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/IngestionReviewPage.tsx",
"sha256": "a49e5aa2bf1b7f509e437b4ccacaea9ea2698a7fa8f5770a29402d8d3527d0ce",
- "bytes": 11124
+ "bytes": 11124,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/KnowledgeEditorRoute.tsx",
"sha256": "7c3cb59c3bfe5841dd7f82505446da3d5822c379479c0bf3c227baeecba681d0",
- "bytes": 2380
+ "bytes": 2380,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/KnowledgeRoutePage.test.tsx",
"sha256": "633e803a017dfedd9646df3682ff51b9feddf33aae26ab25f36b28ee7333fef8",
- "bytes": 7081
+ "bytes": 7081,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/KnowledgeRoutePage.tsx",
"sha256": "78ecf930f452ef844a8236ba62cf97865add07ae8be3bd1f888a5c5a9b6a219c",
- "bytes": 9898
+ "bytes": 9898,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/NotFoundPage.tsx",
"sha256": "2de25eefa9cde911f01e142bf92a4685b48c163bc6a45049181ba3a17a2d1f88",
- "bytes": 573
+ "bytes": 573,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/ProjectWorkspacePage.tsx",
- "sha256": "b15eae6398795dd9b7f06697903e22e6c79b17a9dce8fd60b63ea30d5fba62d0",
- "bytes": 15139
+ "sha256": "b4b979fad45582953d101a6312799d29c625a2b32723a91c60fb8c06f6e47d62",
+ "bytes": 14038,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/ProjectsOverviewPage.test.tsx",
- "sha256": "5e9029619595cef9711220cfebd17ccf1e34744d14f5a1b2697bfbc1114af677",
- "bytes": 9715
+ "sha256": "43fbc3d898abddd9e1aef4d9dc0948665723371bd103914dc755bdcb1e5a5cfe",
+ "bytes": 10674,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/ProjectsOverviewPage.tsx",
- "sha256": "fe84386f4c339db910f592a459311b4dc5308f4734699796285aedf967998af8",
- "bytes": 17151
+ "sha256": "0f30ad59ba40d1ce39b28780e6120922672fc8a605ec95f71715ecf91d688ed1",
+ "bytes": 15292,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/RouteStates.tsx",
"sha256": "65ab0884f4f3882adfbc0a5ebdb41a3a5bc933efcad1aa36f48aa005034070b7",
- "bytes": 1666
+ "bytes": 1666,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/RunbookWorkspacePage.tsx",
"sha256": "d21f3610ed654aea55cdcaa5aa8b60b162ad8c7b65f591d2724686cad52920fc",
- "bytes": 991
+ "bytes": 991,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/RunbooksOverviewPage.test.tsx",
- "sha256": "a431231392ca97c22bb153e617faed724ef521df76f53220f9daae2fe7dc3c05",
- "bytes": 1567
+ "sha256": "411e91d05083a9a3bc43c4d5df68d590e41d37b1dea8a5669c6f4537db7c7544",
+ "bytes": 1657,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/RunbooksOverviewPage.tsx",
- "sha256": "609827d6cda62adf1945355b2b30dc94b76b8525f45f4c77f231ab965918b1d1",
- "bytes": 4010
+ "sha256": "616944e41168a11c96d115dab18e132c396935f26e3aa2cb87a0526574ab37ae",
+ "bytes": 3833,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/pages/v2/SearchRoutePage.test.tsx",
+ "sha256": "5b3dc4782b113fcea02c560727f4dcbd044d4ed54d1c342cd44083c6dc1f01e9",
+ "bytes": 4262,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/SearchRoutePage.tsx",
- "sha256": "bf6cb16f4c0773011538f5c93640fcce12022ed4b1d8ffa541a6e7d8932bb3b5",
- "bytes": 352
+ "sha256": "a89da30c4636d39d76127b7b2b2c6f3ac5e17dac95e8b81df8e564be69f13904",
+ "bytes": 5057,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/ServiceDetailPage.tsx",
- "sha256": "15210ed67f9edc0d5448e58959e9f5ccbf728fdf7161e0e5aa7e6c61f96261df",
- "bytes": 20205
+ "sha256": "501fa79f9dd2795eca580cc0ec8e48c572fc6660c7309c3c1d5f759b0bb2da56",
+ "bytes": 20480,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/ServicesCatalogPage.test.tsx",
- "sha256": "a86a4a185a4d48a65d3d5b35073b97eef3977c944c82722ea454234186e264f1",
- "bytes": 9277
+ "sha256": "0fdc2b9be694fb53a6e4e465a7589565365f76ea9876f20fe60559a48530f3a5",
+ "bytes": 9566,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/ServicesCatalogPage.tsx",
- "sha256": "186446a5ccf3bc61093c161ed9d0cca58fabdd63979b73bb011a9f67e163557c",
- "bytes": 16560
+ "sha256": "373052505fbafc468330b29cc278f41d393d2a8d148092f7adc4afbcd36fe29f",
+ "bytes": 13723,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/SettingsPage.tsx",
- "sha256": "a19495a86a45e7a3a1854e014b092fb45eef17124e0216a4f73288be4d68f143",
- "bytes": 8044
+ "sha256": "118377b7de685aa4d7217bb254d6022b1c1baa3546c5f8f2b97aeff6623d650a",
+ "bytes": 11702,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/StoragePage.test.tsx",
"sha256": "6e7065424c4d29325973217ce32d33ec0b24cf4b6d571769d856fbfe113b2b22",
- "bytes": 2528
+ "bytes": 2528,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/StoragePage.tsx",
"sha256": "12f21266029100c803416d72c98d6fe2ec3692355a643397e1779fc699ab1410",
- "bytes": 8109
+ "bytes": 8109,
+ "mode": "100644"
},
{
"path": "src/frontend/src/pages/v2/ingestionProposalMutations.ts",
"sha256": "cf55775adf60969d96dce338bd7f1eaafb33bcd11cfe245e21e6aa7c6766e11d",
- "bytes": 1401
+ "bytes": 1401,
+ "mode": "100644"
},
{
"path": "src/frontend/src/preferences/PreferencesContext.test.tsx",
- "sha256": "3c6253589bd474435c344d27591b5e0946805112ae1a3ae697c41ad2701cae66",
- "bytes": 7534
+ "sha256": "496fc3990f98cc93168b6d774ec1ad1d1f18a097359dc47b6daea57f33d5db16",
+ "bytes": 16388,
+ "mode": "100644"
},
{
"path": "src/frontend/src/preferences/PreferencesContext.tsx",
- "sha256": "5919e3b6097439653a077fb43a6a374262f6486d9189d61dbc092d08cb20a017",
- "bytes": 6486
+ "sha256": "4c52b7569ce1ae699ace02faf6778257cadc15e717ffa306a495f48d278ee0a2",
+ "bytes": 19098,
+ "mode": "100644"
},
{
"path": "src/frontend/src/preferences/preferences-context.ts",
- "sha256": "28391797b9529468d52ec0573a61be0c1170cacd00cfd8f4c9221b1315b2cf20",
- "bytes": 1538
+ "sha256": "8ff4d8b4e1ed045514fdd42b2ce0d9b8bf20ec3f7428732051b9d7d924df0fb3",
+ "bytes": 2108,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/query/QueryProvider.tsx",
+ "sha256": "8e03b7186056188babf635894f91e7a37d1f025744a50567138beb7941f05191",
+ "bytes": 521,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/query/queryClient.test.ts",
+ "sha256": "79eb2ca94474827f179e127203d63b2fe60dbee79421cd97fa63d36ce4ac3cdb",
+ "bytes": 1639,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/query/queryClient.ts",
+ "sha256": "dfa12620c2fd6a73942c289879a900e2ca339834865779d4a86c041ec0cb1ecd",
+ "bytes": 660,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/query/testPreferencesApi.ts",
+ "sha256": "1135c06bf3c7dd88090af3323e81a68b9b257fb48cf9c509cd8a67d7b89169c3",
+ "bytes": 1439,
+ "mode": "100644"
},
{
"path": "src/frontend/src/router/AuthenticatedRoutes.test.tsx",
- "sha256": "1c2b4a125d1f95c22dd255d80119733ca30ae25b015946090046e77ed02ba0dc",
- "bytes": 9912
+ "sha256": "635cd2b08adef4b3e02ffa1e97b534854f68ffa6dd44b1ae449e98847cbbf753",
+ "bytes": 10796,
+ "mode": "100644"
},
{
"path": "src/frontend/src/router/AuthenticatedRoutes.tsx",
- "sha256": "20462a0607f21728be97e6467d0bc5b7ee3879f6ef7bf12f84b904cd2ccf366a",
- "bytes": 3962
+ "sha256": "7f73391029a3767c7e2610f80b1a912de8d039b3a3d3ea75afd4f49e2728db4e",
+ "bytes": 5786,
+ "mode": "100644"
+ },
+ {
+ "path": "src/frontend/src/test-utils/request.ts",
+ "sha256": "867487599d14f4483587a8c4d27ba6d735076df9936b12d48d35cc19b1db0b68",
+ "bytes": 622,
+ "mode": "100644"
},
{
"path": "src/frontend/src/test/motionTestUtils.tsx",
"sha256": "7a68f4f27f462770e4ef86618e238c7eed82a51a566a0f4cace6c35226197692",
- "bytes": 2159
+ "bytes": 2159,
+ "mode": "100644"
},
{
"path": "src/frontend/src/test/setup.ts",
"sha256": "8782a831c3470856266e93f37c39c071950f68958639143049685386a30628d1",
- "bytes": 1044
+ "bytes": 1044,
+ "mode": "100644"
},
{
"path": "src/frontend/src/visualizations/ProjectArtwork.tsx",
"sha256": "47a32cc9b5d9604d08c84b9d8d1a2989d71a4372cfd10e3db02130c909f08edf",
- "bytes": 1542
+ "bytes": 1542,
+ "mode": "100644"
},
{
"path": "src/frontend/src/visualizations/ProjectSignature.test.tsx",
"sha256": "27c77afbdb5671eadf466fa85a9a1ba71bdf0bb5fd0d7c2ff6f3ee6865c54b71",
- "bytes": 5743
+ "bytes": 5743,
+ "mode": "100644"
},
{
"path": "src/frontend/src/visualizations/ProjectSignature.tsx",
"sha256": "f7bd4c57b82f9be0b5b93e1c74e495e0519a7061207e59d6d90276b7764d6ec8",
- "bytes": 2570
+ "bytes": 2570,
+ "mode": "100644"
},
{
"path": "src/frontend/src/visualizations/ServerMap.test.tsx",
"sha256": "9c152f41578f7c1229dbcce75f3838d25b4968f1759521260573fada01e46faf",
- "bytes": 7736
+ "bytes": 7736,
+ "mode": "100644"
},
{
"path": "src/frontend/src/visualizations/ServerMap.tsx",
"sha256": "e092478b3a5e32c78b15a664ba936c2fc807bf821780a94e75161d44982d261d",
- "bytes": 4998
+ "bytes": 4998,
+ "mode": "100644"
},
{
"path": "src/frontend/src/visualizations/ServiceTopology.test.tsx",
"sha256": "87f1fa37d22494b14bd2cee6d2978c30bba4e9e77ef0533096f8c9adb11f46e0",
- "bytes": 5579
+ "bytes": 5579,
+ "mode": "100644"
},
{
"path": "src/frontend/src/visualizations/ServiceTopology.tsx",
"sha256": "6b8d77a77debdf198ea2c69a8f4912ce4aba842671fd6cb2a6e5795a536beb58",
- "bytes": 2969
+ "bytes": 2969,
+ "mode": "100644"
},
{
"path": "src/frontend/src/visualizations/hostLabel.ts",
"sha256": "0cf72bffc447e4fa8271e7c2f8304f57d9fd3d4c7b7cb6299449940471b43a21",
- "bytes": 834
+ "bytes": 834,
+ "mode": "100644"
},
{
"path": "src/frontend/src/visualizations/projectArtworkGeometry.ts",
"sha256": "8b8b8124736263343b4d2c63882eb5230606395a2e322b0a6128397006e3b5b1",
- "bytes": 996
+ "bytes": 996,
+ "mode": "100644"
},
{
"path": "src/frontend/src/visualizations/projectLogoSource.ts",
"sha256": "88c5123b49d0fdd36f8d20bfddc875e7fa1326154702233a8c7c0d843afd244e",
- "bytes": 2123
+ "bytes": 2123,
+ "mode": "100644"
},
{
"path": "src/frontend/src/vite-env.d.ts",
"sha256": "65996936fbb042915f7b74a200fcdde7e410f32a669b1ab9597cfaa4b0faddb5",
- "bytes": 38
+ "bytes": 38,
+ "mode": "100644"
},
{
"path": "src/frontend/tsconfig.app.json",
"sha256": "787debd064cab0f699e9ded5a1a8d46236a802dd35756871f7d3da65064b7133",
- "bytes": 696
+ "bytes": 696,
+ "mode": "100644"
},
{
"path": "src/frontend/tsconfig.json",
"sha256": "770b4140bbb581e2dfd9ea9946ffc9c75a1d86ba7d2db5f77c83e37cbdf9d808",
- "bytes": 119
+ "bytes": 119,
+ "mode": "100644"
},
{
"path": "src/frontend/tsconfig.node.json",
"sha256": "da44988fbbab51979e9e52dd5f6a9854ff1511880082f4e17e15dc7bbdca067a",
- "bytes": 457
+ "bytes": 457,
+ "mode": "100644"
},
{
"path": "src/frontend/vite.config.ts",
"sha256": "47e30c5389e6189649e55601974c5184b9af9a1a0d8715c320639d7e22f1f553",
- "bytes": 1194
+ "bytes": 1194,
+ "mode": "100644"
},
{
"path": "src/frontend/vitest.config.ts",
- "sha256": "c10fc444e6bd01da9d219f418203e0d483673dacdb91281e0a3e15681872a717",
- "bytes": 912
+ "sha256": "ccd6f7959900ebf9a6064f4b852f440a921895672af194b55b7bb570b17105d0",
+ "bytes": 1080,
+ "mode": "100644"
},
{
"path": "tests/backend/Directory.Build.props",
"sha256": "b0a567b357d9616ffcadbe8b8170643fa98392a0c427ee27deb3cc03eb35646b",
- "bytes": 578
+ "bytes": 578,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Api.Tests/ForwardedHeadersTrustTests.cs",
"sha256": "9aace185b11e25f193677eb235d7f84fb75337217c3d1da79ed00a5d7001a0a7",
- "bytes": 4654
+ "bytes": 4654,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Api.Tests/GlobalUsings.cs",
"sha256": "50436eb3a60162e0cfad749eddfb06f8908f821a90ca44532df946a53e071359",
- "bytes": 20
+ "bytes": 20,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Api.Tests/HealthEndpointTests.cs",
"sha256": "bcb11a815d34a01f5ad5d76e65640a1e2d39e0092786ac83a1fd335f20cadf93",
- "bytes": 1702
+ "bytes": 1702,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Api.Tests/ItWorx.KnowledgeHub.Api.Tests.csproj",
"sha256": "ac500693872ad89e1f82ab15342c895bf4c0427e4db6fbbf3a4bad0bec211ef8",
- "bytes": 719
+ "bytes": 719,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Api.Tests/KnowledgeHubApiFactory.cs",
"sha256": "2be914fd004cdc46b61dbe7c6726c672cb729b2f6a8eab834fd6ee85e67b3b94",
- "bytes": 1145
+ "bytes": 1145,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/backend/ItWorx.KnowledgeHub.Api.Tests/LeafWikiAdapterLifetimeTests.cs",
+ "sha256": "4955eb3000685d79af97413d88705cab8f710e4143907eec9c6b6717efb47b1b",
+ "bytes": 923,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/backend/ItWorx.KnowledgeHub.Api.Tests/PlatformEndpointAuthorizationTests.cs",
+ "sha256": "9d6a9701f80d16bb1b35b1eb52f8491cab27723b5438c29062043709e8412a44",
+ "bytes": 834,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Api.Tests/RestoreModeRunnerTests.cs",
- "sha256": "a929db8f9b7cb5624392ec42c63160965cce78e50081e01ba83bc72f545a0766",
- "bytes": 3535
+ "sha256": "01f727a3152f64856bf0bbd0634b0f2a8483d5418c0b635faf541e111ad56f61",
+ "bytes": 6792,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Api.Tests/SecurityHeaderTests.cs",
"sha256": "455e6322739575e1f3ba4a67c88ec1781e7dc245bdfbe20616a23fb1b7b37657",
- "bytes": 2011
+ "bytes": 2011,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Architecture.Tests/GlobalUsings.cs",
"sha256": "50436eb3a60162e0cfad749eddfb06f8908f821a90ca44532df946a53e071359",
- "bytes": 20
+ "bytes": 20,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Architecture.Tests/ItWorx.KnowledgeHub.Architecture.Tests.csproj",
"sha256": "43044be136fbcca44499bb237f9a3a655c0f7d7499cab9ce0fa1681652ff4c4e",
- "bytes": 511
+ "bytes": 511,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Architecture.Tests/ProjectReferenceTests.cs",
"sha256": "25f0e49444022866c57c246f4fa171c97e11844c0c09a4d2c3a2b9ea5aa173c9",
- "bytes": 2520
+ "bytes": 2520,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/AttachmentUsageTests.cs",
"sha256": "6f8bcb85df3b59b9a4432c304f1329d8053a74a13dc402e8d21d06257d997c31",
- "bytes": 473
+ "bytes": 473,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/BackgroundJobTests.cs",
+ "sha256": "c78ec0760bae2d298b307a2b2c31c23328f7fc3714cfae357135f301ccfa9802",
+ "bytes": 4089,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/DecisionRecordTests.cs",
"sha256": "35ca6db2abf98f84530244b94fd8d10789c333a8b98fd522df1f700aae51a954",
- "bytes": 2919
+ "bytes": 2919,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/DraftTests.cs",
"sha256": "630fa8c6ae9a01e5819bf01cb42a7bf749bf6c1d9d6046c201755a84aacd273c",
- "bytes": 2281
+ "bytes": 2281,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/EmailAddressTests.cs",
"sha256": "441cdd5399caf8ef23b05d7f803b99c4c71c19b8183f2af78217f322954b9c16",
- "bytes": 875
+ "bytes": 875,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/FileSystemPathBoundaryTests.cs",
"sha256": "2ad617bdc1cd392a5f784aa11bb8dbf1d97710a0331187fd85bd17d2b6f48e21",
- "bytes": 881
+ "bytes": 881,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/GlobalUsings.cs",
"sha256": "50436eb3a60162e0cfad749eddfb06f8908f821a90ca44532df946a53e071359",
- "bytes": 20
+ "bytes": 20,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/InfrastructureKnowledgeTests.cs",
- "sha256": "37b87f9661a15c45e6c8da318354190cd68a51f6f9bdc0ce8429e5473df477c6",
- "bytes": 2301
+ "sha256": "a48be3828c4d71f2d07c27d4bba83741a69c4b69bc47a8da3f5e8f39d4e6bac4",
+ "bytes": 2655,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/IngestionProposalTests.cs",
"sha256": "d1fc168870abbb9fb2ea09a164ca8a687ba5e9a4bf750c13fa7c876cbc581389",
- "bytes": 16179
+ "bytes": 16179,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/InstallationTests.cs",
"sha256": "2c2761d5a92acc7328bb3f84a283c8f4b516ec06c07daf334f66098f5f6803be",
- "bytes": 957
+ "bytes": 957,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/ItWorx.KnowledgeHub.Domain.Tests.csproj",
"sha256": "c44e6c3b9ab1dea0f22d4533e74fc89adcbe039118a384e164d844803bbeaf54",
- "bytes": 876
+ "bytes": 876,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/PageCreationSessionTests.cs",
"sha256": "cc0b2cade55bae47275eca75a76eecc042eb9a1a6af5113d38ef91bc5a744fc5",
- "bytes": 2934
+ "bytes": 2934,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/PageTests.cs",
"sha256": "1c1187908b6c9e2f05e04e5dae5d8cbded3a6debc99eaf4b18039efaa48d4edc",
- "bytes": 4642
+ "bytes": 4642,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/PageTitleTests.cs",
"sha256": "c720221a2a8b2cb7cb91508acfec97edcd624774456d1f5cea2bd5656fd83c31",
- "bytes": 781
+ "bytes": 781,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/ProjectTests.cs",
"sha256": "6baa1ac26163e3de2463d57c2003ddf1826c8ec94ab4264fddb78ccc337e99b7",
- "bytes": 3113
+ "bytes": 3113,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/RunbookProfileTests.cs",
"sha256": "30259da4f5ffbd219fa28d4741e0d78f05a3598d824aa7b62aa0e45cdc72ef77",
- "bytes": 1291
+ "bytes": 1291,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/SafeFilenameTests.cs",
"sha256": "fc102dd3034d014bb6fc342a5ab9d85b5b772a94ca4bfbd225fdf9300765b6de",
- "bytes": 1934
+ "bytes": 1934,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/ServiceAccountTests.cs",
"sha256": "139ea348309adae5783b7e3f223944909e68d56b9c12f17cc037f7d3e289a40d",
- "bytes": 3896
+ "bytes": 3896,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/SortKeyTests.cs",
"sha256": "94ca257b33bb77184fe1d067d8b421fc13175c7b481598886d21e33f24f55262",
- "bytes": 1141
+ "bytes": 1141,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/Sources/DataSourceTests.cs",
+ "sha256": "242759504c9155cc14a63ba627537b4984bbc345ae9b026455cfa92039ace4c0",
+ "bytes": 2257,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.Domain.Tests/WorkspaceNameTests.cs",
"sha256": "6452cc4936d52ec4bae52d2b750de955b50faac1d3d7f6e1a7e7c9c996697357",
- "bytes": 1034
+ "bytes": 1034,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Attachments/AttachmentUploadTests.cs",
"sha256": "00b15bf1134d2d8095ea3168678fb50a3fa081fe28baff9899049b3fc7166ce3",
- "bytes": 12430
+ "bytes": 12430,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Attachments/StagedAttachmentAuthorizationTests.cs",
"sha256": "b9f8221062632a2d7338d93eba9feeb7b4da6f749cea477c3f160286cff9194d",
- "bytes": 12107
+ "bytes": 12107,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Auth/ConcurrentLoginTests.cs",
"sha256": "ef8b668d88ed9a53d9db952421a01e4fde541ca305613b7905d9fd5aa5b4545e",
- "bytes": 1176
+ "bytes": 1176,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Auth/CsrfTests.cs",
"sha256": "1fd06004af33335e7a5e6c78ea6b495e1a3f1bac7cd4e27f40c567d91509a8a7",
- "bytes": 1942
+ "bytes": 1942,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Auth/LoginLogoutLockoutTests.cs",
"sha256": "3fd9d8fdec4a169bae6e8e620ba6e57a6f135e19dab4e97dd973b4b4dacb3d09",
- "bytes": 5513
+ "bytes": 5513,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Auth/RecoveryTests.cs",
"sha256": "79570d35497ea473070d36cfd7873e0406b0c7272642c0f581b1216d2f6eb3b1",
- "bytes": 6817
+ "bytes": 6817,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Auth/SessionExpiryTests.cs",
"sha256": "4ddbb62765e695fc0b6f2bd7dbc0e521aead5d08d6fcb64ac5723dfb5ce3e134",
- "bytes": 1403
+ "bytes": 1403,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Auth/WorkspaceMemberSearchTests.cs",
"sha256": "e06e00a180859067661a417ee930f9710bdbb4991ce38a337e111c3f1f0748ce",
- "bytes": 3933
+ "bytes": 3933,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Authorization/RoleAuthorizationTests.cs",
- "sha256": "fd4269fdae83874b088c5a91177e6bcf4f34004d01229e0d218d3a4fd8ba833c",
- "bytes": 5290
+ "sha256": "6fe1de8392d956d02a4867ec9490cc4ab86e76ddd1832c97aaee5b5988dddd1c",
+ "bytes": 5279,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Automation/IdempotencyTests.cs",
"sha256": "444abd821152e08670d1bda0b6c0e27ada696f85bb1abf2ee9968fc459d02040",
- "bytes": 4400
+ "bytes": 4400,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Automation/OutboxTests.cs",
"sha256": "0c3d6780ab8f5983b26d8064350e231685f5d3c429b55bc75e8a4ef58726d9f8",
- "bytes": 4302
+ "bytes": 4302,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Automation/ProjectContextTests.cs",
"sha256": "c291aac579f522c775f480c4576f1d6434e44fd362e571bfaf13d6a2a1c1b19d",
- "bytes": 4781
+ "bytes": 4781,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Automation/ServiceAccountTests.cs",
"sha256": "176001168511217b11b766324de9060087d6510e76e7b4f3543c339e06068cca",
- "bytes": 19737
+ "bytes": 19737,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Automation/ServiceAccountTokenIssuerTests.cs",
"sha256": "8f50ed1460b7d8d3b57bb61d70483aa5c7ce1052892c342c9a0311bbc9014748",
- "bytes": 2342
+ "bytes": 2342,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/GlobalUsings.cs",
"sha256": "50436eb3a60162e0cfad749eddfb06f8908f821a90ca44532df946a53e071359",
- "bytes": 20
+ "bytes": 20,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Infrastructure/KnowledgeHubIntegrationFactory.cs",
- "sha256": "d8a4803a44bf92699a33010106c4626d4b9c0e368c5817a24c4c0cec02dba11c",
- "bytes": 6309
+ "sha256": "3c2f04933568944fd668ad87f7889ab3bd5be2b9d8631c1be2378b8d22a5a03c",
+ "bytes": 6636,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Infrastructure/OwnerSetupTestBase.cs",
"sha256": "81a3e9a3653433178f44d47c064c57db81e1a6473ec2f6a2eb4a4aaad2b5fa11",
- "bytes": 1599
+ "bytes": 1599,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Infrastructure/PostgresContainerFixture.cs",
- "sha256": "e2e309740077baed94d23fba8c3b46076ac7c1cb7024cfd839e09bce7f4cbbdf",
- "bytes": 2249
+ "sha256": "28cd5848662edb7bbfb9f05980bb879c8ab2ea0c527d36d6a505dc59aeb87623",
+ "bytes": 2627,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Infrastructure/PostgresTestGroup.cs",
"sha256": "acad3cf4cc8f0bada688837f4b5babf750cbf5b7d6701f3f0071df6978a43fa1",
- "bytes": 225
+ "bytes": 225,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Infrastructure/SetupHelper.cs",
"sha256": "8d07df84d6fa27625e06ec65355036c3b09aae03b87f38a98d80e59df70e6d0b",
- "bytes": 5230
+ "bytes": 5230,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/InfrastructureKnowledge/EditorialProfileValidationTests.cs",
"sha256": "5ddbc9f870f008e1d977bb65170e3b8bc709957969c9884c48bcd5233917cf51",
- "bytes": 11578
+ "bytes": 11578,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/InfrastructureKnowledge/ExpandedManualOverrideCatalogTests.cs",
"sha256": "c869f264f0c2006c031b32f07448b0f63878d0a40405a2426468ed78d3706d1c",
- "bytes": 9307
+ "bytes": 9307,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/InfrastructureKnowledge/InfrastructureCatalogReleaseScaleTests.cs",
"sha256": "63d7471175327a5953e0541514adae8e761fcf8ba0a087dd36938fef2f8a5af7",
- "bytes": 12611
+ "bytes": 12611,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/InfrastructureKnowledge/InfrastructureCatalogScaleTests.cs",
"sha256": "70bf7e2bf08bbe3593a091be680a173f3bca87083f400ee819874123d22ee6a8",
- "bytes": 8715
+ "bytes": 8715,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/InfrastructureKnowledge/InfrastructurePaginationTests.cs",
"sha256": "8606278c03d5c3bc09bcf27e62b1c570d59379c1e16bfe93dfccf5d88ab501a3",
- "bytes": 12149
+ "bytes": 12149,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/InfrastructureKnowledge/InfrastructureSynchronizationTests.cs",
- "sha256": "8551e55a4bcc65e88e0e43336f0fad44f2062cc0030b129c92be59f4778065e2",
- "bytes": 30073
+ "sha256": "0eb042f8f37edea1d5e88c0751856b9b59ffeaf58a89c9570a2b6f1b55ca1898",
+ "bytes": 35041,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/InfrastructureKnowledge/ManualOverrideCatalogTests.cs",
"sha256": "352183037e11e4ebd2123798c73324d6faeb79fe26b111c1e2aaf5e4f8143da9",
- "bytes": 9483
+ "bytes": 9483,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Ingestion/IngestionObservabilityTests.cs",
"sha256": "b4425360c31ddc9af65e6dc025b07ebecb301aa6b0a6f7add272199aed0e87e2",
- "bytes": 9748
+ "bytes": 9748,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Ingestion/IngestionPilotAvailabilityTests.cs",
"sha256": "7599a942d9cc6d948bfa7942db9e324ffb63a340d6a3b45e67dfccdd901934bc",
- "bytes": 2838
+ "bytes": 2838,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Ingestion/IngestionPilotContractTests.cs",
"sha256": "8f60385ccdcb312d188801ad0e61c22497ec6cadcebff7220ae6334abe160a60",
- "bytes": 15287
+ "bytes": 15287,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Ingestion/IngestionProposalApiTests.cs",
"sha256": "8643b011fa8621b68aee41bf8e54bbb8eb5085f06954b51013c9708fde7b642a",
- "bytes": 43019
+ "bytes": 43019,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Ingestion/IngestionProposalAuthorizationTests.cs",
"sha256": "87a1b67f6beaef4151a141cc622eb6abf557b6424da096bec0e83603988a9156",
- "bytes": 9371
+ "bytes": 9371,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Ingestion/IngestionProposalEtagContractTests.cs",
"sha256": "570749cd19e820ad837fd622860904ec170650b65441a80273a8d74657183a48",
- "bytes": 4535
+ "bytes": 4535,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Ingestion/IngestionProposalPersistenceTests.cs",
"sha256": "d07601fbdf2d7f405e59a8d0c81aa374dc7ea8b198a1297e028780e2bde2de9d",
- "bytes": 8420
+ "bytes": 8420,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Ingestion/IngestionProposalPrivacyTests.cs",
"sha256": "114d88cc28609bb30eda2167e7d0d4c258f2feafc42730813d9bac4462d9c595",
- "bytes": 11741
+ "bytes": 11741,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Ingestion/IngestionProposalSourceCompensationTests.cs",
"sha256": "dce305a08f045cf9d33e567da1344d677d98a1338241a93643a314fdeced9097",
- "bytes": 12190
+ "bytes": 12190,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Ingestion/IngestionPublicationTests.cs",
"sha256": "ed579474d0a6ff66696baac5a55ca0602d2d19c0d8abea2363a143a78b03e898",
- "bytes": 20199
+ "bytes": 20199,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Ingestion/IngestionSourceRetentionPersistenceTests.cs",
"sha256": "f40ed6fc2d1012e0f1333ee26747aeb465be2b3665f17d6876b2b46da9dac4b2",
- "bytes": 9354
+ "bytes": 9354,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Ingestion/IngestionSourceRetentionTests.cs",
"sha256": "ebc00d50c7a4ab35d7422ad27ca21eb2ae23f520d373e671647649cb293d9cef",
- "bytes": 9840
+ "bytes": 9840,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/ItWorx.KnowledgeHub.IntegrationTests.csproj",
"sha256": "89b9209c9a6f02cdc697b25fc57cc385d65e64c08552101ed960791c9d18e060",
- "bytes": 1834
+ "bytes": 1834,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Jobs/BackgroundJobConsistencyTests.cs",
+ "sha256": "ebe3c11f8fc9b502429b5427450fb1041fec91e1042bec98a002be7d25a7974e",
+ "bytes": 12399,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Jobs/BackgroundJobWorkerTests.cs",
+ "sha256": "431c7d52b1ff318835f6b5e95189519aff0495de3bd5f6df8e3bc1aa20601557",
+ "bytes": 11083,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Jobs/BackgroundJobsApiTests.cs",
+ "sha256": "38c960297cb42aa5430623a970eb176ae342365d7be8e3c09ade188bbdf0ca95",
+ "bytes": 29783,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Knowledge/BacklinksTests.cs",
"sha256": "b4d44afd5f5ef42260c7e658a04aea2569c2fc326ace5a88a94bb9de20df2a9c",
- "bytes": 3646
+ "bytes": 3646,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Knowledge/DraftAutosaveTests.cs",
"sha256": "2a3052b4b69f9928e5164183104f9b07992cb4c1fcd715b466199efe900f71de",
- "bytes": 4500
+ "bytes": 4500,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Knowledge/InternalLinkResolutionTests.cs",
"sha256": "515ea3013a1863ccd1879040875bce5ae8532ce93694e2db339875208a200f5f",
- "bytes": 4975
+ "bytes": 4975,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Knowledge/MarkdownSanitizationTests.cs",
"sha256": "8d4e180987c311f388b2c93c74689f15f8f58b0b7d56ece98350b334c27fa648",
- "bytes": 10526
+ "bytes": 10526,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Knowledge/PageConcurrencyTests.cs",
"sha256": "b1d6a793a66b0403a2808b5c33cee011e67461bdefce33bc351419351b9370bd",
- "bytes": 3835
+ "bytes": 3835,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Knowledge/PageCreationStagingTests.cs",
"sha256": "aaa2b6c1edd544baae60e0f9ecc562275072c60a0f5678f730f2e11989641b33",
- "bytes": 13089
+ "bytes": 13089,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Knowledge/PageCrudTests.cs",
"sha256": "2e8b63081fc159a3623af57ef6f0daff6fb668dccbf9f08a10fa2c2cd134ebb7",
- "bytes": 9087
+ "bytes": 9087,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Knowledge/PageTreeScaleTests.cs",
"sha256": "6b066fc33c57ad51d1498bf1482ab79f0f885a98c65c99df0935153fd2006073",
- "bytes": 4162
+ "bytes": 4162,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Knowledge/RagPublicationManifestTests.cs",
"sha256": "b39ca25e0b74d490280b3900d35b2468c822c9104676dadce6f882ddc7a8f7f4",
- "bytes": 13618
+ "bytes": 13618,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Knowledge/RevisionsTests.cs",
"sha256": "fca318dbe303eb869cda7dbbbfc900c5e9baa8a08af0a70fecc060ec4d3d68d6",
- "bytes": 5046
+ "bytes": 5046,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Knowledge/SearchFullScaleTests.cs",
- "sha256": "bc93cf09fab0c37c5689bbdc33aa51af6de99858e72eec955181a2d4bad112be",
- "bytes": 21113
+ "sha256": "6baa425d6723a88f2e0698ea6363cfe4af803c6a4b844c19420fcb114e3609c3",
+ "bytes": 21637,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Knowledge/SearchScaleTests.cs",
"sha256": "8febc36432eaae5642c762018c06c62873c5221a3288f2c86ffe8a468d73b62f",
- "bytes": 4075
+ "bytes": 4075,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Knowledge/SearchTests.cs",
"sha256": "ed8ffb2b302f238d9e21d52c7ff55e99af3b9659d97fe6a085e70cb56ae41761",
- "bytes": 6270
+ "bytes": 6270,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Knowledge/StagingSessionCleanupTests.cs",
"sha256": "0fe285c8e68ca30d56042e6c6afae953dd7f54dc6762c38766a8500bcc4f0e86",
- "bytes": 15574
+ "bytes": 15574,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Knowledge/TemplateTests.cs",
"sha256": "72376ccc16b54beb2c9a035c070fd3b26dfaa6f980f5b49d73447eb18cb3fb66",
- "bytes": 2842
+ "bytes": 2842,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Knowledge/TrashPurgeTests.cs",
"sha256": "7c6ea2e7ceebf0542fc8ec040569f635e24213097d3794a3a317670105ecdcc5",
- "bytes": 4606
+ "bytes": 4606,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Migration/CutoverRehearsalTests.cs",
"sha256": "dd6aadc78a9f2416d801ebe4d1049a28310e9c836bf1c2d21668422c3a9b175a",
- "bytes": 13214
+ "bytes": 13214,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Migration/InPlaceUpgradeRehearsalTests.cs",
"sha256": "6be5365f778870dd38a63d9552ccb672da9e181737380b73e1e74828b6a523f1",
- "bytes": 9921
+ "bytes": 9921,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Migration/LeafWikiImportTests.cs",
"sha256": "7bb69f38364e6854c7c533b1d15a7b2365feee742139ca69fddaa7439c14f57a",
- "bytes": 27489
+ "bytes": 27489,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Migration/LeafWikiV5ImportTests.cs",
"sha256": "5839381bc9b01d3d9124f98aae1ee8085f74cbd8d690f235293eb5e1d68b12fa",
- "bytes": 15377
+ "bytes": 15377,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Migration/SourceBatchPurgeTests.cs",
"sha256": "0c6190b595bef291b3f8df07652ba5d004fd01a94433a27b3bbd58aecf219edb",
- "bytes": 8874
+ "bytes": 8874,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Operations/ActivityFeedTests.cs",
- "sha256": "5439e76efa08f3588fad4f3dde3ace611a789aecdc1292b5557ecf3c58b8988c",
- "bytes": 1472
+ "sha256": "4fb20ddfab5c58821883ea6f76888ba2f2ece0033dad47049a683bd4ceffcd4f",
+ "bytes": 2861,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Operations/BackupEvidenceTests.cs",
+ "sha256": "2dfb388042098a921bdadfe077774a71fb83b17bda7cee159525d0cb8c3439d6",
+ "bytes": 8333,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Operations/BackupServiceTests.cs",
- "sha256": "a533eabdb6a606bc9d9db6ed33910b97c0e80ac572b50bfd5f54327921ff914c",
- "bytes": 4196
+ "sha256": "2815b7f3833d8f0eae746bfd3da9d823a8b00b226a68d6366fd810ae8043a085",
+ "bytes": 8268,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Operations/DataSourceApiTests.cs",
+ "sha256": "5c9c675febda722a0cf84ab7c27cbc50c2890c01dd549c5424ecbd92a8c980c6",
+ "bytes": 3951,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Operations/HealthCheckTests.cs",
"sha256": "ce0a7755a62645880fc0619089c004cb33db0ca7f3bb45eda09f4d0a14566557",
- "bytes": 5519
+ "bytes": 5519,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Operations/LoginRedactionTests.cs",
"sha256": "fa1438b0483dd76b2485377b7e9ce40b11e77d4ebbbe46dc57239ec3a551329a",
- "bytes": 2449
+ "bytes": 2449,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Operations/OtlpExportTests.cs",
+ "sha256": "849256e3e06d889a8f74999ee8be74c5b8ad1f5c8829d41760c59376694e5e83",
+ "bytes": 2576,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Operations/PlatformMetricsTests.cs",
+ "sha256": "0d7a37d580f8e9fb9c79746a51db508d010555186379d18ddf73b7e97dd62c0a",
+ "bytes": 1308,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Operations/RestoreServiceTests.cs",
- "sha256": "e18e516bd4586574b39d880ce1e52cf989d25c5b37242405b7283bac64c0a923",
- "bytes": 8438
+ "sha256": "0f8b0228a3aa7c568c0a52d4952e757698e8d68f45bbd8bdf9da073edbc9c463",
+ "bytes": 20974,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Operations/SchemaGuardTests.cs",
"sha256": "15a5441e5248229d69d6c0af53c10b2b2b4287fb8a60a0caa2c3bb7827bf06af",
- "bytes": 4448
+ "bytes": 4448,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Operations/SystemInfoTests.cs",
- "sha256": "52fee800671424b2c7123602e7ec9ec1bb743118c68ae190d93e3068ad9ba537",
- "bytes": 3119
+ "sha256": "802713cf37854709dae5bbb15d02cb69852809d89f2229b62e326d539240c9b4",
+ "bytes": 3499,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Operations/TableIdentifierSafetyTests.cs",
+ "sha256": "8b76dd13abf8a3c1951502d76eabb5190b71bfdbe4878ab5a063503e12e8d28f",
+ "bytes": 9818,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Platform/PlatformMigrationTests.cs",
+ "sha256": "8ceebe49b39a4e4dff41ad732c465b9d2af7f104e86831fdcc107616f2d9a9c8",
+ "bytes": 2374,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Platform/PlatformSettingsApiTests.cs",
+ "sha256": "cb79bd3435f9c50cf635a8cc60ba43a2e5df81140c4d8b561a636a47681a423d",
+ "bytes": 5316,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Platform/UserPreferencesApiTests.cs",
+ "sha256": "12743f53c55c2ebce1df06799af1e2ddcae6009d14cdca3e295428887b433cdd",
+ "bytes": 7004,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Projects/DecisionTests.cs",
"sha256": "add1093964043ae40de795fedd3f9c5d9fa2af54dd30264dba796b131ab8f002",
- "bytes": 7772
+ "bytes": 7772,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Projects/ProjectHubTests.cs",
"sha256": "9934bfd60c1286730ea9e315da1641c706e5b804d9b09c99e2cfbdfad9c82e6d",
- "bytes": 18936
+ "bytes": 18936,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Projects/RunbookTests.cs",
"sha256": "1c03d35c0f293c89947ebec214bcc0a1fd04b75a472d67c2c0ed66c880bda430",
- "bytes": 6683
+ "bytes": 6683,
+ "mode": "100644"
},
{
"path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Setup/FirstRunSetupTests.cs",
"sha256": "65e49d37e0256c2ec14ed2ea94f782beb8da177a0585409ba1a128dff87bc8b5",
- "bytes": 2617
+ "bytes": 2617,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/UnifiedSearch/UnifiedSearchScaleTests.cs",
+ "sha256": "94249bb2b44f72710a861feb7e03005dda27067c35a3c7cce02f6ebfb21b57f8",
+ "bytes": 5758,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/backend/ItWorx.KnowledgeHub.IntegrationTests/UnifiedSearch/UnifiedSearchTests.cs",
+ "sha256": "59c3ca3e2906a2715fc28b1bd64aaec35f74d2a51eac55c4697f69c331f9e0f5",
+ "bytes": 19341,
+ "mode": "100644"
},
{
"path": "tests/e2e/global-setup.ts",
"sha256": "87142c3c6dc78d3cff7b0bfb4ef76ec7bd6c35eb052023a408f8003cf2aaef61",
- "bytes": 4101
+ "bytes": 4101,
+ "mode": "100644"
},
{
"path": "tests/e2e/package.json",
- "sha256": "9f9484c32e1edfdb76206fef167b5517b9a06b55bc3d6de548d0040889912b2b",
- "bytes": 398
+ "sha256": "8d74629efb8a0c226085e81e05f881b9242eb998f32cdc1465427a43371e67c2",
+ "bytes": 413,
+ "mode": "100644"
},
{
"path": "tests/e2e/playwright.config.ts",
- "sha256": "ad749af57a54caa7ae3161b9b9a0e69daa692eae7ea0a730a63a0d72f601f4f0",
- "bytes": 944
+ "sha256": "942c3bd6293e6b2771697bcb669a94834429ec16e065affe06e65f6348dcc8cd",
+ "bytes": 1379,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/attachments.spec.ts",
"sha256": "c05f2645157e15a72c4ce6288ed1244248b14e27c7d4fc10b3f26d104068beaa",
- "bytes": 3024
+ "bytes": 3024,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/auth-journey.spec.ts",
"sha256": "f89bb871fe6494a9a786c992e39da15c7a2b4bb45e61c2d2668f9aab69ed82ae",
- "bytes": 2576
+ "bytes": 2576,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/baseline.spec.ts",
"sha256": "3fad54bff5595955a16898aabfadef48451b5bf89a39f039cd35c6db74bc032b",
- "bytes": 773
+ "bytes": 773,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/discovery.spec.ts",
- "sha256": "1ab37f9fb35e3b470e585a4fefb2324b4791e96fc358b8fb745f667260583893",
- "bytes": 5462
+ "sha256": "0bde0c4eb49179260e2eeaf94fe6d13429d38830d07e57dd61ed800a33d1120e",
+ "bytes": 6001,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/ingestion-review.spec.ts",
- "sha256": "1536ee1e6260d9f20f0be89007b6af2fdfd8b9974f0e9a30a0a0a0f1a82fc7ef",
- "bytes": 4100
+ "sha256": "5ec75d1f124d2d0335e514769091a2259c75cb75129add25ed64a4cbc90d6a67",
+ "bytes": 4212,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/knowledge-editing.spec.ts",
"sha256": "ff9c1ba0974d9860c3e34ff9b2109c9c2b03e2529acfe84240b711afa6c02e19",
- "bytes": 7003
+ "bytes": 7003,
+ "mode": "100644"
+ },
+ {
+ "path": "tests/e2e/specs/platform-productization.spec.ts",
+ "sha256": "ee4e1871dd9e57231b32d1506d9fd8fa637e08c935cb2a3b1a7644d9fed9a7ed",
+ "bytes": 6616,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/project-hub.spec.ts",
"sha256": "eb5d70422430b58cbfbfb354928bc9581b140793486c901f9ae2896434994bfc",
- "bytes": 9315
+ "bytes": 9315,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/support/credentials.ts",
"sha256": "439e2847b04fb7c2858b5e441391a1de808ee826b9603299271e3d76b8e033c9",
- "bytes": 411
+ "bytes": 411,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/support/v2.ts",
"sha256": "b4f8131748087dae86b4746ba80cd7581a00d9a1737abfb65b72fbebb407c4d6",
- "bytes": 2960
+ "bytes": 2960,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/v2-editor-leafwiki-floor.spec.ts",
- "sha256": "f24f4203bf9f6f2f5e9a159112010c910b7e1662265fe6144bd3de51e878aee2",
- "bytes": 10847
+ "sha256": "f4e09179e3192e3359e0467c60946f8e49bf2ff21b251bb95f7c2c695b58e3b4",
+ "bytes": 11350,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/v2-infrastructure-review.spec.ts",
- "sha256": "5718cd25ced1e7b8ce204b41d70d166662a07dfa5afb06ea65de718a5c9e5850",
- "bytes": 9358
+ "sha256": "64ba86233675ab1fae9dca82063317339eb2d3e94efc943903a1c3b4d1755b7a",
+ "bytes": 9378,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/v2-m5-workspace-fidelity.spec.ts",
- "sha256": "a73b3c05027b1f7cd54daaaacc08a18288056d080d75cd4e66245a5427cf5be1",
- "bytes": 7430
+ "sha256": "3c219de21e7d5affbb21fe068f4865f790679c791eff2e3c901dd6a5e0623363",
+ "bytes": 7440,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/v2-m6-motion-and-matrices.spec.ts",
- "sha256": "8288720d427ca6e2ababd538df25cf7eec95a9897119da22863745d4c08ba4ab",
- "bytes": 6928
+ "sha256": "8a6ff943998177465a6f5a6090ec770607d6e2004e1a5db81f3b83fe38efb51b",
+ "bytes": 8029,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/v2-m7-admin-actions.spec.ts",
- "sha256": "33b59a667459a76ee1f574244b6f1e0cc479f1f689d90d8cbac43c979d76e0d8",
- "bytes": 10604
+ "sha256": "918818bfb3a3e5ef917923824f74f2f6b2a1fc6e0276788a4a370018a77e9457",
+ "bytes": 11704,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/v2-m8-accessibility-and-fidelity.spec.ts",
- "sha256": "a1de19825eb55b3103f3174797c909e3a393e3b079dad034de71f4c0801c4cb5",
- "bytes": 8986
+ "sha256": "e4e55625075e96b29b51d5f70869f32bd45a985c742c163578b7696ae652281c",
+ "bytes": 8996,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/v2-r3-daily-ux-completeness.spec.ts",
"sha256": "1787d051c085de0cdc8e9cbdeeffdd6786eb23add8b1eb12e35207fbec195ad2",
- "bytes": 6706
+ "bytes": 6706,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/v2-routing-and-fidelity.spec.ts",
- "sha256": "fc6823cf7cffdee2deea7c63f951c4098b6a66f799a03961788a8e802b53c2e6",
- "bytes": 13598
+ "sha256": "89742264ffa0b700afc105199d680def9b5e6738c2b05dcd72aba9fa202bb0da",
+ "bytes": 13798,
+ "mode": "100644"
},
{
"path": "tests/e2e/specs/v3-intake-pilot.spec.ts",
"sha256": "98f62bf9087c5463e15094e543c192fa1045ee5d28a8af14812c5e62ce6cc4db",
- "bytes": 10361
+ "bytes": 10361,
+ "mode": "100644"
},
{
"path": "tools/leafwiki-inspector/README.md",
"sha256": "24bae3b3a276cf7e84f3908c8e5f3b7b0920d377dba65f355e5b9d219fe689e9",
- "bytes": 1359
+ "bytes": 1359,
+ "mode": "100644"
},
{
"path": "tools/leafwiki-inspector/inspect.py",
"sha256": "10239ed5ed307bb0ac7b6d03900e1a6335aec1f7d58364b444174cd0f2d106d0",
- "bytes": 19648
+ "bytes": 19648,
+ "mode": "100644"
},
{
"path": "tools/leafwiki-inspector/tests/test_inspect.py",
"sha256": "d4d508b7760d9b83326ab90798685dcf474576c2598f626b4f822986ac6aa8f2",
- "bytes": 4098
+ "bytes": 4098,
+ "mode": "100644"
}
]
}
diff --git a/README.md b/README.md
index 32a6f57..1b285fa 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,10 @@ this repository.
- Track projects, milestones, updates, architecture decisions and runbook validation.
- Import LeafWiki content through a read-only, preflighted and reversible workflow.
- Create scoped, revocable service-account tokens for the versioned `/api/v1` API.
+- Synchronize personal preferences across devices and manage versioned platform policy.
+- Follow durable course-tagging and backup jobs with live progress and cancellation.
+- Search knowledge, projects, services, runbooks and decisions from one workspace.
+- Manage source freshness and inspect recorded deployment, recovery and telemetry evidence.
- Back up the database and attachments together, then verify a clean-target restore.
## Screenshots
diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile
index f3fb013..70a2a82 100644
--- a/deploy/docker/Dockerfile
+++ b/deploy/docker/Dockerfile
@@ -57,8 +57,10 @@ ENV ASPNETCORE_URLS=http://0.0.0.0:8080 \
DOTNET_EnableDiagnostics=0 \
Build__Commit=${BUILD_COMMIT}
+# Explicitly refresh OpenSSL too: the pinned base may predate security
+# updates even when curl's already-satisfied dependencies would keep it.
RUN apt-get update \
- && apt-get install --yes --no-install-recommends curl ca-certificates \
+ && apt-get install --yes --no-install-recommends curl ca-certificates libssl3t64 openssl \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --gid 10001 knowledgehub \
&& useradd --uid 10001 --gid 10001 --no-create-home --shell /usr/sbin/nologin knowledgehub \
diff --git a/deploy/observability/collector.yaml b/deploy/observability/collector.yaml
new file mode 100644
index 0000000..e659865
--- /dev/null
+++ b/deploy/observability/collector.yaml
@@ -0,0 +1,24 @@
+receivers:
+ otlp:
+ protocols:
+ http:
+ endpoint: 0.0.0.0:4318
+
+processors:
+ memory_limiter:
+ check_interval: 1s
+ limit_mib: 128
+ batch:
+ timeout: 5s
+
+exporters:
+ prometheus:
+ endpoint: 0.0.0.0:8889
+ metric_expiration: 2m
+
+service:
+ pipelines:
+ metrics:
+ receivers: [otlp]
+ processors: [memory_limiter, batch]
+ exporters: [prometheus]
diff --git a/deploy/observability/compose.yaml b/deploy/observability/compose.yaml
new file mode 100644
index 0000000..7ac8bc4
--- /dev/null
+++ b/deploy/observability/compose.yaml
@@ -0,0 +1,47 @@
+services:
+ app:
+ environment:
+ OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector:4318
+ OTEL_EXPORTER_OTLP_PROTOCOL: http/protobuf
+ OTEL_METRIC_EXPORT_INTERVAL: "15000"
+
+ otel-collector:
+ image: ${KNOWLEDGE_HUB_COLLECTOR_IMAGE:-otel/opentelemetry-collector-contrib:0.162.0-amd64@sha256:340885ab6f46822374f00c58c067396b53b8d612559842e8821585567579d9e3}
+ restart: unless-stopped
+ command: ["--config=/etc/otelcol-contrib/config.yaml"]
+ volumes:
+ - ./deploy/observability/collector.yaml:/etc/otelcol-contrib/config.yaml:ro
+ read_only: true
+ security_opt: ["no-new-privileges:true"]
+ networks: [knowledge-hub-internal]
+
+ prometheus:
+ image: prom/prometheus:v3.14.0@sha256:5ce7540c3c00ef4ab0c9d2c995c6a5b9c421f44b4a115d97a2c7af3b1c21cbb0
+ restart: unless-stopped
+ command: ["--config.file=/etc/prometheus/prometheus.yml", "--storage.tsdb.path=/prometheus", "--storage.tsdb.retention.time=7d"]
+ volumes:
+ - ./deploy/observability/prometheus.yaml:/etc/prometheus/prometheus.yml:ro
+ - prometheus-data:/prometheus
+ read_only: true
+ security_opt: ["no-new-privileges:true"]
+ networks: [knowledge-hub-internal]
+
+ grafana:
+ image: grafana/grafana:12.4.0@sha256:b0ae311af06228bcfd4a620504b653db80f5b91e94dc3dc2a5b7dab202bcde20
+ restart: unless-stopped
+ environment:
+ GF_SECURITY_ADMIN_USER: ${KNOWLEDGE_HUB_GRAFANA_USER:-admin}
+ GF_SECURITY_ADMIN_PASSWORD: ${KNOWLEDGE_HUB_GRAFANA_PASSWORD:?Set a private Grafana password for observability}
+ GF_USERS_ALLOW_SIGN_UP: "false"
+ volumes:
+ - ./deploy/observability/grafana-datasource.yaml:/etc/grafana/provisioning/datasources/knowledge-hub.yaml:ro
+ - grafana-data:/var/lib/grafana
+ ports:
+ - "127.0.0.1:${KNOWLEDGE_HUB_GRAFANA_PORT:-13000}:3000"
+ read_only: true
+ security_opt: ["no-new-privileges:true"]
+ networks: [knowledge-hub-internal]
+
+volumes:
+ prometheus-data:
+ grafana-data:
diff --git a/deploy/observability/grafana-datasource.yaml b/deploy/observability/grafana-datasource.yaml
new file mode 100644
index 0000000..08d76c5
--- /dev/null
+++ b/deploy/observability/grafana-datasource.yaml
@@ -0,0 +1,8 @@
+apiVersion: 1
+datasources:
+ - name: Knowledge Hub Prometheus
+ type: prometheus
+ access: proxy
+ url: http://prometheus:9090
+ isDefault: true
+ editable: false
diff --git a/deploy/observability/prometheus.yaml b/deploy/observability/prometheus.yaml
new file mode 100644
index 0000000..ece3cf9
--- /dev/null
+++ b/deploy/observability/prometheus.yaml
@@ -0,0 +1,7 @@
+global:
+ scrape_interval: 15s
+ evaluation_interval: 15s
+scrape_configs:
+ - job_name: itworx-knowledge-hub
+ static_configs:
+ - targets: [otel-collector:8889]
diff --git a/docker-compose.yml b/docker-compose.yml
index db8db13..ab54df6 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -65,6 +65,10 @@ services:
KnowledgeHub__Paths__Backups: /var/lib/knowledge-hub/backups
KnowledgeHub__Paths__Keys: /var/lib/knowledge-hub/keys
Build__Commit: ${BUILD_COMMIT:-development}
+ # Explicit opt-in export to the operator's collector. Empty disables export.
+ OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-}
+ OTEL_EXPORTER_OTLP_PROTOCOL: ${OTEL_EXPORTER_OTLP_PROTOCOL:-http/protobuf}
+ OTEL_METRIC_EXPORT_INTERVAL: ${OTEL_METRIC_EXPORT_INTERVAL:-30000}
TZ: ${TZ:-Europe/Brussels}
ports:
- "${KNOWLEDGE_HUB_PORT:-1240}:8080"
diff --git a/docs/12-auth-security.md b/docs/12-auth-security.md
index 516bf45..c09cfe2 100644
--- a/docs/12-auth-security.md
+++ b/docs/12-auth-security.md
@@ -186,6 +186,13 @@ At minimum isolate policies for:
Return a generic response that does not confirm account/content existence.
+The knowledge and unified-search endpoints share a 60-request/minute budget per
+effective remote IP. Job creation and the compatibility backup-creation endpoint
+share a 10-request/minute budget. Both use zero-length queues and return 429.
+Forwarded addresses are accepted only from configured trusted proxies. Durable
+job admission additionally enforces per-actor, workspace and rolling daily budgets
+inside PostgreSQL; see [background jobs](background-jobs.md).
+
## Security verification
Release requires:
diff --git a/docs/18-backup-restore.md b/docs/18-backup-restore.md
index 2ba13b5..2033843 100644
--- a/docs/18-backup-restore.md
+++ b/docs/18-backup-restore.md
@@ -16,7 +16,8 @@ A full backup set contains:
5. Product, schema and container/image versions.
6. File inventory with sizes and SHA-256 hashes.
7. Backup job metadata and timestamps.
-8. Encryption metadata when operator-managed encryption is enabled.
+8. A signed integrity receipt authenticating the exact manifest.
+9. Encryption metadata when operator-managed encryption is enabled.
Do not include runtime session secrets or plaintext API tokens.
@@ -42,13 +43,46 @@ mutable files. Deletions use retention, preventing a blob from disappearing duri
5. copy/link/archive required blobs without following unsafe paths;
6. create manifest;
7. verify dump readability and every file hash;
-8. optionally encrypt with an operator-supplied mechanism;
-9. atomically publish completed backup;
-10. apply retention only after a verified new backup exists;
-11. record audit and administrator-visible result.
+8. authenticate the manifest hash with HMAC-SHA-256;
+9. optionally encrypt with an operator-supplied mechanism;
+10. atomically publish completed backup;
+11. apply retention only after a verified new backup exists;
+12. record audit and administrator-visible result.
A failed backup never replaces the last known-good backup.
+New backup directories and files use private Unix permissions (0700 and 0600).
+Permission failures stop creation before publication. On Windows, provision and
+verify restrictive NTFS ACLs for backups, keys and restore targets before use;
+Unix mode checks do not enforce Windows ACLs. HMAC authenticates backups but
+does not encrypt their contents, so archive/share access must remain restricted.
+
+## Backup authenticity key and recovery
+
+The `.integrity.json` receipt beside a backup is not trusted by itself. The application
+authenticates its backup ID, exact manifest hash, completion time and verification result
+with HMAC-SHA-256. The random 32-byte key is stored at
+`KnowledgeHub:Paths:Keys/backup-integrity.key`; in the supplied Compose deployment this
+is `/var/lib/knowledge-hub/keys/backup-integrity.key`. This location is outside
+`KnowledgeHub:Paths:Backups`, so someone who can rewrite a backup and its receipt cannot
+create a valid signature. The application creates the key when integrity evidence is
+first written and restricts it to the service account on Unix hosts.
+
+Treat this key as essential recovery material. Copy the exact key file into an encrypted,
+access-controlled escrow location separate from the backup storage, and include it in
+the operator's disaster-recovery procedure. To restore on a replacement host, recover
+the key to the configured Keys directory before verifying or restoring backups. Never
+put the key in a backup directory or in the same backup archive as the signed receipts.
+Protect access to the escrow copy as carefully as access to the backups.
+
+If the key is missing or unreadable, backup integrity is reported as unknown and restore
+is refused. Do not replace a lost key with a newly generated one and expect existing
+backups to validate: a new key authenticates only backups created after it. Recover the
+original key from escrow first. If no copy exists, the existing backups cannot be
+cryptographically authenticated by the application and must not be treated as verified.
+Backups made before authenticated receipts were introduced also remain unverified; create
+a new backup with this version before relying on application-verified restore.
+
## Retention defaults
Configurable starting policy:
@@ -65,13 +99,26 @@ source.
Restore is deliberately separate from normal application startup.
-1. Select backup.
-2. Verify manifest, hashes, version compatibility and encryption.
-3. Create clean PostgreSQL database and empty target directories.
-4. Restore database.
+1. Select backup. Copy read-only media to a writable recovery directory first,
+ preserving the manifest, signed receipts and file bytes. Recover the exact
+ `backup-integrity.key` from protected external escrow into the configured Keys
+ directory; never take it from the backup archive. The application acquires an
+ exclusive storage lease there to prevent retention from removing a restore source,
+ and writes its restore receipt there. Keep the original media unchanged.
+2. Use the backup's recorded application image to create a clean PostgreSQL
+ database at exactly the manifest's schema version. Create an existing, empty
+ target attachments directory on writable storage with room for the full payload.
+3. Copy the manifest and every listed database and attachment file into a private
+ staging directory inside the target attachments volume. Verify the HMAC against
+ the staged manifest bytes and verify each staged file's size and SHA-256 before
+ opening or changing the target database. Restore reads only from this verified
+ snapshot, so source-volume changes after verification cannot change restored bytes.
+4. Restore database. The restore service refuses a target whose latest applied
+ migration differs from the authenticated manifest, before inserting any rows.
5. Restore attachment blobs by hash.
-6. Start the recorded/current compatible application in maintenance mode.
-7. Apply supported migrations.
+6. Verify the restored data with the recorded application version.
+7. Upgrade through supported migrations using the intended newer image, then
+ repeat integrity checks before promotion.
8. Run integrity checks:
- FK/schema;
- user/membership;
@@ -87,6 +134,21 @@ Restore is deliberately separate from normal application startup.
Never test restore by overwriting the only production instance.
+Pass target PostgreSQL credentials through `--target-connection-file=`, which
+reads the connection string from a protected file. On Unix, the file must be readable
+only by the restore account (mode `0600` or `0400`); the restore command rejects files
+with group or other access. On Windows, apply an ACL that grants access only to the
+restore operator/service account. Do not put the connection string in command-line
+arguments. Restore errors deliberately omit provider exception details because they
+can contain connection data.
+
+The real-backup drill also requires the exact integrity key recovered from protected
+external escrow. Pass only its file path through
+`KNOWLEDGE_HUB_BACKUP_INTEGRITY_KEY_FILE`; the script copies the key with mode `0600`
+into its temporary working directory, uses that directory as the restore Keys path,
+and removes the temporary copy during cleanup. The key contents are never placed in an
+argument or printed by the script.
+
## Backup encryption
The application may integrate an operator-supplied encryption command or library after
@@ -125,11 +187,12 @@ isolated restore proves it is necessary and the operator has assessed newer writ
- create backup with representative data;
- verify manifest;
+- reject a forged manifest and matching unsigned/invalid integrity receipt;
- corrupt dump and blob tests;
- clean-target restore;
- compare logical counts and hashes;
- restore older supported schema then migrate;
-- missing key/encrypted backup failure;
+- missing integrity key or encryption key failure;
- interrupted backup cleanup;
- retention policy;
- no secret inclusion;
diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml
index 92dc9a6..5073b0d 100644
--- a/docs/api/openapi.yaml
+++ b/docs/api/openapi.yaml
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: ItWorx Knowledge Hub API
- version: 0.1.0-draft
+ version: 1.0.0-rc.1
description: |
Versioned API for the ItWorx Knowledge Hub web client and scoped automation.
This is not an MCP transport. The future MCP service will consume this API.
@@ -29,6 +29,9 @@ tags:
- name: Activity
- name: Workspace
- name: Ingestion proposals
+ - name: Preferences
+ - name: DataSources
+ - name: Jobs
security:
- cookieAuth: []
- bearerAuth: []
@@ -777,6 +780,59 @@ paths:
$ref: "#/components/schemas/SearchResponse"
"401":
$ref: "#/components/responses/Unauthorized"
+ /unified-search:
+ get:
+ tags: [Search]
+ operationId: searchWorkspaceContent
+ description: >-
+ Human reader session only. Searches visible knowledge, projects, reviewed
+ services, runbooks and decisions in the current workspace. Trashed content
+ and hidden or archived services are excluded. Counts cover all five kinds;
+ `kind` filters the returned page. A `tag` filters exact, case-insensitive
+ page tags on knowledge, runbook and decision results and excludes projects
+ and services. An empty query returns no results unless `tag` is supplied. The
+ endpoint shares the search rate limit of 60 requests per effective remote IP
+ per minute. An invalid `kind` returns 422.
+ parameters:
+ - name: q
+ in: query
+ schema:
+ type: string
+ maxLength: 500
+ - name: kind
+ in: query
+ schema:
+ type: string
+ enum: [knowledge, projects, services, runbooks, decisions]
+ - name: tag
+ in: query
+ description: Exact, case-insensitive page tag; applies to knowledge, runbooks and decisions.
+ schema:
+ type: string
+ maxLength: 64
+ - $ref: "#/components/parameters/Cursor"
+ - name: limit
+ in: query
+ schema:
+ type: integer
+ minimum: 1
+ maximum: 100
+ default: 25
+ responses:
+ "200":
+ description: Permission-filtered, ranked results and complete grouped counts
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/UnifiedSearchResponse"
+ "401":
+ $ref: "#/components/responses/Unauthorized"
+ "403":
+ $ref: "#/components/responses/Forbidden"
+ "422":
+ description: Invalid kind
+ "429":
+ description: Search request budget exhausted
/rag/publication-manifest:
get:
tags: [Rag]
@@ -3077,6 +3133,180 @@ paths:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
+ /me/preferences:
+ get:
+ tags: [Preferences]
+ operationId: getMyPreferences
+ security: [{ cookieAuth: [] }]
+ responses:
+ "200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/UserPreferences" } } } }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ put:
+ tags: [Preferences]
+ operationId: updateMyPreferences
+ security: [{ cookieAuth: [] }]
+ requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/UserPreferencesRequest" } } } }
+ responses:
+ "200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/UserPreferences" } } } }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "412": { $ref: "#/components/responses/PreconditionFailed" }
+ "428": { $ref: "#/components/responses/PreconditionRequired" }
+ /admin/platform-settings:
+ get:
+ tags: [Admin]
+ operationId: getPlatformSettings
+ security: [{ cookieAuth: [] }]
+ responses:
+ "200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/PlatformSettings" } } } }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ put:
+ tags: [Admin]
+ operationId: updatePlatformSettings
+ security: [{ cookieAuth: [] }]
+ requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/PlatformSettingsRequest" } } } }
+ responses:
+ "200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/PlatformSettings" } } } }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "412": { $ref: "#/components/responses/PreconditionFailed" }
+ "428": { $ref: "#/components/responses/PreconditionRequired" }
+ /admin/data-sources:
+ get:
+ tags: [DataSources]
+ operationId: listDataSources
+ security: [{ cookieAuth: [] }]
+ responses:
+ "200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/DataSourceList" } } } }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ post:
+ tags: [DataSources]
+ operationId: registerDataSource
+ security: [{ cookieAuth: [] }]
+ requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/DataSourceRegistration" } } } }
+ responses:
+ "200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/DataSource" } } } }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ /admin/data-sources/{id}:
+ put:
+ tags: [DataSources]
+ operationId: configureDataSource
+ security: [{ cookieAuth: [] }]
+ parameters: [{ name: id, in: path, required: true, schema: { type: string, format: uuid } }]
+ requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/DataSourceConfiguration" } } } }
+ responses:
+ "200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/DataSource" } } } }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "412": { $ref: "#/components/responses/PreconditionFailed" }
+ "428": { $ref: "#/components/responses/PreconditionRequired" }
+ /admin/data-sources/{id}/observations:
+ post:
+ tags: [DataSources]
+ operationId: reportDataSourceObservation
+ security: [{ cookieAuth: [] }]
+ parameters: [{ name: id, in: path, required: true, schema: { type: string, format: uuid } }]
+ requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/DataSourceObservation" } } } }
+ responses:
+ "200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/DataSource" } } } }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ /admin/backups/status:
+ get:
+ tags: [Backups]
+ operationId: getBackupStatus
+ security: [{ cookieAuth: [] }]
+ responses:
+ "200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackupStatus" } } } }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ /admin/backups/{backupId}/protection:
+ put:
+ tags: [Backups]
+ operationId: setBackupProtection
+ security: [{ cookieAuth: [] }]
+ parameters: [{ name: backupId, in: path, required: true, schema: { type: string } }]
+ requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/BackupProtectionRequest" } } } }
+ responses:
+ "200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackupEvidence" } } } }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "412": { $ref: "#/components/responses/PreconditionFailed" }
+ "428": { $ref: "#/components/responses/PreconditionRequired" }
+ /admin/audit-log:
+ get:
+ tags: [Admin]
+ operationId: listAdminAuditLog
+ security: [{ cookieAuth: [] }]
+ responses:
+ "200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/AdminAuditList" } } } }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ /jobs:
+ get:
+ tags: [Jobs]
+ operationId: listBackgroundJobs
+ security: [{ cookieAuth: [] }]
+ responses:
+ "200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackgroundJobList" } } } }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ /jobs/course-tagging:
+ post:
+ tags: [Jobs]
+ operationId: queueCourseTaggingJob
+ security: [{ cookieAuth: [] }]
+ parameters: [{ $ref: "#/components/parameters/RequiredIdempotencyKey" }]
+ requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/QueueCourseTaggingJobRequest" } } } }
+ responses:
+ "202": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackgroundJob" } } } }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ /jobs/backups:
+ post:
+ tags: [Jobs]
+ operationId: queueBackupJob
+ security: [{ cookieAuth: [] }]
+ parameters: [{ $ref: "#/components/parameters/RequiredIdempotencyKey" }]
+ requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/QueueBackupJobRequest" } } } }
+ responses:
+ "202": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackgroundJob" } } } }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ /jobs/{jobId}:
+ get:
+ tags: [Jobs]
+ operationId: getBackgroundJob
+ security: [{ cookieAuth: [] }]
+ parameters: [{ name: jobId, in: path, required: true, schema: { type: string, format: uuid } }]
+ responses:
+ "200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackgroundJob" } } } }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ /jobs/{jobId}/cancel:
+ post:
+ tags: [Jobs]
+ operationId: cancelBackgroundJob
+ security: [{ cookieAuth: [] }]
+ parameters: [{ name: jobId, in: path, required: true, schema: { type: string, format: uuid } }]
+ responses:
+ "200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackgroundJob" } } } }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ /jobs/{jobId}/events:
+ get:
+ tags: [Jobs]
+ operationId: streamBackgroundJobEvents
+ security: [{ cookieAuth: [] }]
+ parameters: [{ name: jobId, in: path, required: true, schema: { type: string, format: uuid } }]
+ responses:
+ "200": { description: Job and progress Server-Sent Events with heartbeat and terminal close, content: { text/event-stream: { schema: { type: string } } } }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
components:
securitySchemes:
cookieAuth:
@@ -3248,6 +3478,194 @@ components:
schema:
$ref: "#/components/schemas/ProblemDetails"
schemas:
+ UserPreferencesRequest:
+ type: object
+ required: [theme, density, primaryNavigationCollapsed, contextPanelOpen, navigationOrder, dashboardWidgetOrder, hiddenDashboardWidgets, defaultLandingPage, catalogView, contextPanelWidth, navigationWidth]
+ properties:
+ theme: { type: string, enum: [system, light, dark] }
+ density: { type: string, enum: [comfortable, compact] }
+ primaryNavigationCollapsed: { type: boolean }
+ contextPanelOpen: { type: boolean }
+ navigationOrder: { type: array, items: { type: string } }
+ dashboardWidgetOrder: { type: array, items: { type: string } }
+ hiddenDashboardWidgets: { type: array, items: { type: string } }
+ defaultLandingPage: { type: string }
+ catalogView: { type: string, enum: [grid, list] }
+ contextPanelWidth: { type: integer, minimum: 256, maximum: 440 }
+ navigationWidth: { type: integer, minimum: 192, maximum: 320 }
+ UserPreferences:
+ allOf:
+ - $ref: "#/components/schemas/UserPreferencesRequest"
+ - type: object
+ required: [schemaVersion, eTag, isDefault, updatedAt]
+ properties:
+ schemaVersion: { type: integer, enum: [1] }
+ eTag: { type: string }
+ isDefault: { type: boolean }
+ updatedAt: { type: [string, "null"], format: date-time }
+ PlatformSettingsRequest:
+ type: object
+ required: [displayName, primaryLocale, timeZone, ingestionProposals, backupPolicyExpectation, inventoryFreshnessPolicy]
+ properties:
+ displayName: { type: string, minLength: 1, maxLength: 200 }
+ primaryLocale: { type: string }
+ timeZone: { type: string }
+ ingestionProposals: { type: object, required: [enabled, maximumSourceCount, maximumSourceBytes, failedRetryLimit], properties: { enabled: { type: boolean }, maximumSourceCount: { type: integer, minimum: 1, maximum: 20 }, maximumSourceBytes: { type: integer, format: int64 }, failedRetryLimit: { type: integer, minimum: 1, maximum: 3 } } }
+ backupPolicyExpectation: { type: object, required: [expectedIntervalHours, scheduleDescription, dailyRetentionCount, weeklyRetentionCount, monthlyRetentionCount, protectedBackupCount, restoreVerificationMaxAgeDays], properties: { expectedIntervalHours: { type: integer }, scheduleDescription: { type: string }, dailyRetentionCount: { type: integer }, weeklyRetentionCount: { type: integer }, monthlyRetentionCount: { type: integer }, protectedBackupCount: { type: integer }, restoreVerificationMaxAgeDays: { type: integer } } }
+ inventoryFreshnessPolicy: { type: object, required: [warningAfterHours], properties: { warningAfterHours: { type: integer } } }
+ PlatformSettings:
+ allOf:
+ - $ref: "#/components/schemas/PlatformSettingsRequest"
+ - type: object
+ required: [schemaVersion, eTag, isDefault, updatedAt]
+ properties:
+ schemaVersion: { type: integer, enum: [1] }
+ eTag: { type: string }
+ isDefault: { type: boolean }
+ updatedAt: { type: [string, "null"], format: date-time }
+ backupPolicyExpectation: { type: object, description: Includes enforcement External; configured schedule is an expectation, not a scheduler run. }
+ DataSourceRegistration:
+ type: object
+ required: [sourceKey, displayName, kind, expectedIntervalHours]
+ properties:
+ sourceKey: { type: string, maxLength: 200 }
+ displayName: { type: string, maxLength: 120 }
+ kind: { type: string, enum: [Inventory, Gitea, KnowledgeClient, ManualIntake, LeafWiki, CuratedPackage] }
+ expectedIntervalHours: { type: integer, minimum: 1, maximum: 8760 }
+ DataSourceConfiguration:
+ type: object
+ required: [displayName, enabled, expectedIntervalHours]
+ properties:
+ displayName: { type: string, maxLength: 120 }
+ enabled: { type: boolean }
+ expectedIntervalHours: { type: integer, minimum: 1, maximum: 8760 }
+ DataSourceObservation:
+ type: object
+ required: [success]
+ properties:
+ success: { type: boolean }
+ recordCount: { type: [integer, "null"], format: int64 }
+ errorCode: { type: [string, "null"] }
+ correlationId: { type: [string, "null"], maxLength: 100 }
+ DataSource:
+ type: object
+ required: [id, sourceKey, displayName, kind, enabled, state, configurationState, syncAuthority, expectedIntervalHours, eTag, actions]
+ properties:
+ id: { type: string, format: uuid }
+ sourceKey: { type: string }
+ displayName: { type: string }
+ kind: { type: string }
+ enabled: { type: boolean }
+ state: { type: string, enum: [Unknown, Current, Stale, Failed, Disabled] }
+ configurationState: { type: string }
+ syncAuthority: { type: string }
+ expectedIntervalHours: { type: integer }
+ lastAttemptAt: { type: [string, "null"], format: date-time }
+ lastSuccessAt: { type: [string, "null"], format: date-time }
+ lastFailureAt: { type: [string, "null"], format: date-time }
+ dataObservedAt: { type: [string, "null"], format: date-time }
+ recordCount: { type: [integer, "null"], format: int64 }
+ lastErrorCode: { type: [string, "null"] }
+ lastCorrelationId: { type: [string, "null"] }
+ eTag: { type: string }
+ actions: { type: array, items: { type: string } }
+ DataSourceList:
+ type: object
+ required: [items, nextCursor]
+ properties:
+ items: { type: array, items: { $ref: "#/components/schemas/DataSource" } }
+ nextCursor: { type: [string, "null"] }
+ BackupProtectionRequest:
+ type: object
+ required: [protected]
+ properties:
+ protected: { type: boolean }
+ BackupEvidence:
+ type: object
+ required: [protected, protectionVersion]
+ properties:
+ protected: { type: boolean }
+ protectionVersion: { type: integer }
+ integrityVerifiedAt: { type: [string, "null"], format: date-time }
+ restoreVerifiedAt: { type: [string, "null"], format: date-time }
+ lastRestoreSucceeded: { type: [boolean, "null"] }
+ BackupStatus:
+ type: object
+ required: [state, scheduleAuthority, scheduleDescription, restoreConfidence, storedBackupBytes, protectedCount, expectedIntervalHours, applicationRetentionCount, dailyRetentionExpectation, weeklyRetentionExpectation, monthlyRetentionExpectation, restoreVerificationMaxAgeDays]
+ properties:
+ state: { type: string, enum: [Unknown, Current, Stale] }
+ scheduleAuthority: { type: string, enum: [External] }
+ scheduleDescription: { type: string }
+ nextScheduledRun: { type: [string, "null"], format: date-time }
+ nextExpectedBy: { type: [string, "null"], format: date-time }
+ lastSuccessfulBackupAt: { type: [string, "null"], format: date-time }
+ lastIntegrityVerifiedAt: { type: [string, "null"], format: date-time }
+ lastVerifiedRestoreAt: { type: [string, "null"], format: date-time }
+ restoreConfidence: { type: string, enum: [Unknown, Stale, Verified] }
+ storedBackupBytes: { type: integer, format: int64, description: Sum of stored backup manifest sizes; not filesystem capacity. }
+ protectedCount: { type: integer }
+ expectedIntervalHours: { type: integer }
+ applicationRetentionCount: { type: integer }
+ dailyRetentionExpectation: { type: integer }
+ weeklyRetentionExpectation: { type: integer }
+ monthlyRetentionExpectation: { type: integer }
+ restoreVerificationMaxAgeDays: { type: integer }
+ AdminAuditList:
+ type: object
+ required: [items, nextCursor]
+ properties:
+ items: { type: array, items: { $ref: "#/components/schemas/AdminAuditItem" } }
+ nextCursor: { type: [integer, "null"], format: int64 }
+ AdminAuditItem:
+ type: object
+ required: [id, actorKind, source, action, summary, occurredAt]
+ properties:
+ id: { type: integer, format: int64 }
+ actorKind: { type: string }
+ actorId: { type: [string, "null"], format: uuid }
+ source: { type: string }
+ action: { type: string }
+ targetType: { type: [string, "null"] }
+ targetId: { type: [string, "null"] }
+ summary: { type: string }
+ occurredAt: { type: string, format: date-time }
+ correlationId: { type: [string, "null"] }
+ QueueCourseTaggingJobRequest:
+ type: object
+ required: [rootPageId, tag]
+ properties:
+ rootPageId: { type: string, format: uuid }
+ tag: { type: string }
+ QueueBackupJobRequest: { type: object, description: Empty object; the job snapshots the current server-side backup policy. }
+ BackgroundJob:
+ type: object
+ required: [id, workspaceId, actorId, type, status, createdAt, cancelRequested, progress, total, processed, succeeded, skipped, failed, errors, version]
+ properties:
+ id: { type: string, format: uuid }
+ workspaceId: { type: string, format: uuid }
+ actorId: { type: string, format: uuid }
+ type: { type: string }
+ status: { type: string, enum: [queued, running, succeeded, partially_succeeded, failed, cancelled] }
+ createdAt: { type: string, format: date-time }
+ startedAt: { type: [string, "null"], format: date-time }
+ completedAt: { type: [string, "null"], format: date-time }
+ cancelRequestedAt: { type: [string, "null"], format: date-time }
+ cancelRequested: { type: boolean }
+ progress: { type: integer, minimum: 0, maximum: 100 }
+ total: { type: integer }
+ processed: { type: integer }
+ succeeded: { type: integer }
+ skipped: { type: integer }
+ failed: { type: integer }
+ errors: { type: array, items: { type: object, properties: { code: { type: string }, message: { type: string }, itemId: { type: [string, "null"], format: uuid } } } }
+ result: { type: [object, "null"] }
+ correlationId: { type: [string, "null"] }
+ version: { type: integer, format: int64 }
+ BackgroundJobList:
+ type: object
+ required: [items]
+ properties:
+ items: { type: array, items: { $ref: "#/components/schemas/BackgroundJob" } }
CreateIngestionProposalRequest:
type: object
required: [manifest]
@@ -5243,6 +5661,41 @@ components:
$ref: "#/components/schemas/SearchResultItem"
nextCursor:
type: [string, "null"]
+ UnifiedSearchResponse:
+ type: object
+ required: [items, counts, nextCursor]
+ properties:
+ items:
+ type: array
+ items:
+ $ref: "#/components/schemas/UnifiedSearchResultItem"
+ counts:
+ $ref: "#/components/schemas/UnifiedSearchCounts"
+ nextCursor:
+ type: [string, "null"]
+ UnifiedSearchCounts:
+ type: object
+ required: [knowledge, projects, services, runbooks, decisions, total]
+ properties:
+ knowledge: { type: integer, minimum: 0 }
+ projects: { type: integer, minimum: 0 }
+ services: { type: integer, minimum: 0 }
+ runbooks: { type: integer, minimum: 0 }
+ decisions: { type: integer, minimum: 0 }
+ total: { type: integer, minimum: 0 }
+ UnifiedSearchResultItem:
+ type: object
+ required: [kind, id, title, summary, snippet, updatedAt, href]
+ properties:
+ kind:
+ type: string
+ enum: [knowledge, projects, services, runbooks, decisions]
+ id: { type: string, format: uuid }
+ title: { type: string }
+ summary: { type: string }
+ snippet: { type: string }
+ updatedAt: { type: string, format: date-time }
+ href: { type: string, description: Same-origin relative route to the result. }
RagPublicationManifestItem:
type: object
description: >-
diff --git a/docs/background-jobs.md b/docs/background-jobs.md
new file mode 100644
index 0000000..49d2b0d
--- /dev/null
+++ b/docs/background-jobs.md
@@ -0,0 +1,43 @@
+# Durable background jobs
+
+Course tagging and manual backup creation run as PostgreSQL-backed jobs. The admin
+workspace shows persisted progress and supports cancellation. SSE sends the same
+safe snapshot as the polling endpoint and closes on a terminal state; the browser
+falls back to polling after an interrupted stream.
+
+Only an enabled workspace administrator can create or execute a job. Mutation
+endpoints require cookie authentication and CSRF validation. The worker rechecks
+the actor's membership before work and before each course page. An idempotency key
+is scoped to the workspace and actor; reusing it with a different payload returns
+409. Retrying the same payload returns the existing job, including at capacity.
+The job row and its `job.queued` audit event commit together. Cancellation requests
+and their `job.cancel_requested` audit event also commit together; repeating a
+running-job cancellation returns the current request without adding another audit
+event.
+
+The database serializes admission per workspace. Defaults under `KnowledgeHub:Jobs`
+are `MaxPendingPerWorkspace=10`, `MaxPendingPerActor=3`, and
+`MaxCreatedPerDay=100` (a rolling 24-hour window). Capacity rejection returns 429
+before inserting a job or page snapshot. HTTP creation also has a shared budget
+of ten requests per effective remote IP per minute. Only configured trusted
+proxies can affect that address. These limits bound distinct-key floods across
+application restarts and replicas as well as ordinary bursts.
+
+Course tagging snapshots at most `MaxCoursePages=100000` page IDs in PostgreSQL.
+The worker updates one current page at a time with its ETag, preserves content,
+skips existing tags, and records safe per-page failures. A lease is renewed while
+work is active; expired leases can be reclaimed with bounded retries. Default
+worker concurrency is two. Cancellation stops future items and preserves the
+already completed outcomes.
+
+Terminal jobs retain their counters, bounded errors, result, actor and audit trail.
+Their page-ID recovery snapshots are removed in the same transaction as the final
+state. This limits large item-table growth without discarding the job history.
+The daily admission budget bounds growth of that compact history. Source data is
+never removed by this maintenance.
+
+The job `Version` is a monotonic optimistic-concurrency token for persisted worker
+updates and SSE event IDs. If a cancellation update wins a race with a tracked worker
+save, the worker retries against the current version while keeping the cancellation
+request. SSE always emits a terminal event and closes when it observes a terminal
+job, including when that snapshot has the same version as the last progress event.
diff --git a/docs/guides/back-ups-herstel-en-unraid-beheer.md b/docs/guides/back-ups-herstel-en-unraid-beheer.md
index fcba059..d48b01e 100644
--- a/docs/guides/back-ups-herstel-en-unraid-beheer.md
+++ b/docs/guides/back-ups-herstel-en-unraid-beheer.md
@@ -65,20 +65,46 @@ af.
### Stap 2 — herstellen
+Maak eerst een **beschrijfbare herstelkopie** van de gekozen back-up; herstel neemt een
+exclusieve opslaglease en schrijft achteraf een herstelbewijs naast het manifest. Geef
+de CLI de doelconnectie via een door de operator beheerd geheimenbestand, niet via een
+argument met het wachtwoord erin. Op Unix moet dat bestand alleen voor de restoregebruiker
+leesbaar zijn (`chmod 600` of `chmod 400`); op Windows beperk je de ACL tot de restore-
+operator/serviceaccount. Het bestand bevat één PostgreSQL-connectiereeks. De
+integriteitssleutel moet eveneens beschikbaar zijn in de geconfigureerde Keys-map.
+Haal de exacte `backup-integrity.key` uit beschermde externe escrow; herstel vertrouwt
+geen sleutel die alleen in de back-upkopie zit. De restore verifieert de manifest-
+handtekening en kopieert de vermelde bestanden eerst naar een privé-stagingmap binnen
+het lege doelbijlagenvolume. Reserveer daar vrije ruimte voor ten minste de volledige
+back-upinhoud. De restore gebruikt uitsluitend die geverifieerde snapshot.
+
```bash
docker run --rm \
--network itworx-knowledge-hub_knowledge-hub-internal \
- -v knowledge-hub-backups:/var/lib/knowledge-hub/backups:ro \
+ -v :/var/lib/knowledge-hub/backups:rw \
-e KnowledgeHub__Paths__Backups=/var/lib/knowledge-hub/backups \
+ -v :/var/lib/knowledge-hub/keys:ro \
+ -e KnowledgeHub__Paths__Keys=/var/lib/knowledge-hub/keys \
+ -v :/run/secrets/restore-target-connection:ro \
-v :/var/lib/knowledge-hub/attachments \
itworx/knowledge-hub:local \
dotnet ItWorx.KnowledgeHub.Api.dll \
--restore \
--backup-id= \
- --target-connection="Host=;Port=5432;Database=;Username=;Password=" \
+ --target-connection-file=/run/secrets/restore-target-connection \
--target-attachments=/var/lib/knowledge-hub/attachments
```
+De echte herstel-drill haalt de back-up via SSH op, maar leest de integriteitssleutel
+uit een lokaal door de operator beheerd escrowbestand. Geef alleen het bestandspad mee;
+de drill maakt tijdens uitvoering een tijdelijke kopie met modus `0600` en verwijdert
+die bij opruimen:
+
+```bash
+KNOWLEDGE_HUB_BACKUP_INTEGRITY_KEY_FILE=/secure/escrow/backup-integrity.key \
+ bash scripts/v2-r4-real-backup-restore-drill.sh
+```
+
De doeldatabase moet **leeg** zijn — herstel weigert expliciet als er al rijen in
staan, om nooit per ongeluk bestaande gegevens te vermengen met herstelde gegevens.
@@ -86,7 +112,8 @@ Het proces rapporteert per tabel het aantal herstelde versus verwachte rijen, he
aantal herstelde bijlagen, en sluit af met `GESLAAGD` of `MISLUKT`
(afsluitcode 0 respectievelijk 1). Een `MISLUKT`-resultaat laat expliciet zien welke
tabel niet overeenkomt — er wordt nooit stilzwijgend een onvolledig herstel als
-geslaagd gerapporteerd.
+geslaagd gerapporteerd. Providerfoutdetails worden niet afgedrukt omdat die
+connectiegegevens kunnen bevatten.
## Opstartgedrag en migraties
diff --git a/docs/observability-platform.md b/docs/observability-platform.md
new file mode 100644
index 0000000..41c85c2
--- /dev/null
+++ b/docs/observability-platform.md
@@ -0,0 +1,16 @@
+# Operationele metrieken
+
+De applicatie exporteert alleen naar OTLP als `OTEL_EXPORTER_OTLP_ENDPOINT` expliciet is gezet. De optionele Compose-overlay `deploy/observability/compose.yaml` schakelt een Collector, Prometheus en Grafana in op een intern Docker-netwerk. Grafana bindt uitsluitend aan de loopback-interface van de host; kies een privaat beheerderswachtwoord via `KNOWLEDGE_HUB_GRAFANA_PASSWORD` buiten Git. Start met beide Compose-bestanden in hetzelfde project nadat de configuratie en opslaglocaties zijn gecontroleerd.
+
+De Collector neemt OTLP/HTTP op poort 4318 aan en biedt de metrieken aan Prometheus op 8889. Grafana leest Prometheus als geprovisioneerde gegevensbron. De applicatie heeft vaste labels voor operatie, uitkomst, jobstatus en bewijstype. SQL, paginatitels, actoren, zoektermen, tokens en bron-URL's zijn geen metrieklabels.
+
+De overlay gebruikt vastgelegde image-digests. De collector-default is de
+beschikbare amd64-build van 0.162.0 voor de Unraid-host. Kies op een andere
+architectuur expliciet een geverifieerde image via `KNOWLEDGE_HUB_COLLECTOR_IMAGE`.
+De ongekwalificeerde tag `0.162.0` was tijdens de controle niet beschikbaar.
+
+Gebruik in Prometheus onder meer `platform_operations_total`, `platform_operation_duration_seconds`, `platform_jobs`, `platform_queue_age_seconds` en `platform_evidence_age_seconds`. Een ontbrekende meting betekent onbekend: na een databasefout worden oudere snapshots uit de gauges verwijderd. De ouderdom van backups telt pas als het nieuwste opgeslagen manifest een geldig integriteitsbewijs heeft. De `inventory`-ouderdom is gebaseerd op de capturetijd van de nieuwste snapshot, niet op de latere importtijd. De applicatie toont het beleid voor externe backups als verwachting, zonder een planner-run te verzinnen.
+
+Controleer na start een echte taak of zoekactie, vraag dan binnen een export- en scrape-interval `platform_operations_total` op via de Prometheus API en controleer de tijdreeks ook in Grafana. Een lege tijdreeks is geen bewijs van een gezonde pipeline. De geïsoleerde geautomatiseerde OTLP-test verstuurt een echte protobuf-aanvraag naar een lokale receiver en controleert dat private labels ontbreken; de volledige Collector → Prometheus → Grafana-pipeline moet in de beoogde omgeving apart worden geverifieerd.
+
+Bronnen voor de gebruikte exporterconfiguratie: [OpenTelemetry Collector Prometheus exporter](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/exporter/prometheusexporter/README.md) en [OTLP exporter configuratie](https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/).
diff --git a/docs/platform-settings.md b/docs/platform-settings.md
new file mode 100644
index 0000000..4713eb2
--- /dev/null
+++ b/docs/platform-settings.md
@@ -0,0 +1,60 @@
+# Voorkeuren en platformbeheer
+
+## Persoonlijke instellingen
+
+Op `/settings` kies je thema, datadichtheid, panelen, landingspagina en de volgorde
+van navigatie en dashboardwidgets. Aangemelde gebruikers bewaren deze keuzes bij
+hun account. Het scherm toont laden, opslaan, gesynchroniseerd, offline of een
+versieconflict. Bij een conflict kun je de servervoorkeuren laden of je lokale
+keuze opnieuw aanbieden. Gebruik de verplaatsknoppen met toetsenbord of touch;
+slepen is een extra mogelijkheid.
+
+## Beheerwerkruimten
+
+`/admin` bevat het overzicht. De afzonderlijke onderdelen behandelen gebruikers,
+toegang, integraties, inventaris, kennis, classificaties, achtergrondtaken,
+back-ups, audit, platforminstellingen en systeeminformatie. Alleen bevoegde
+beheerders hebben toegang. Een verborgen navigatielink vervangt geen API-controle.
+
+Op `/admin/platform` bewaar je weergavenaam, locale, tijdzone, intakebeleid,
+inventarisouderdom en verwachtingen voor externe backups. Verouderde formulieren
+kunnen geen nieuwere serverversie overschrijven. Laad bij een conflict de actuele
+waarden opnieuw voordat je wijzigingen opnieuw toepast.
+
+De backupplanning en dagelijkse, wekelijkse en maandelijkse bewaaraantallen zijn
+verwachtingen over operatorbeheer. Dit scherm start geen externe planner en voert
+geen externe retentie uit. Handmatige backups verschijnen als duurzame taken op
+`/admin/jobs`; de lokale backupdienst hanteert daarnaast haar geconfigureerde
+retentie en respecteert beschermde backups. Zie [backup en herstel](18-backup-restore.md).
+
+## Externe bronnen
+
+Op `/admin/integrations` registreer je een unieke broncode, type, naam en verwacht
+synchronisatie-interval. Inventarisbronnen worden automatisch geregistreerd bij
+een geslaagde synchronisatie. Configuratie en waarnemingen zijn afzonderlijk:
+een ingeschakelde bron is nog geen bewijs dat de laatste run geslaagd is.
+
+De broncode `inventory:default` is gereserveerd voor de inventarisimporter en kan
+niet handmatig worden geregistreerd. Uitschakelen van deze bron blokkeert volgende
+imports. De importer controleert en vergrendelt de bron binnen dezelfde transactie
+als de inventariswijziging: een eerder bevestigde uitschakeling wordt ook door een
+reeds gestarte aanvraag gerespecteerd.
+
+Een operator kan een geslaagde of mislukte externe run rapporteren met een
+bekende recordtelling en correlatie-ID. Een onbekend aantal blijft leeg. Zo'n
+rapportage is geen uitgevoerde verbindingstest. De hub voert externe synchronisatie
+niet zelf uit en bewaart hier geen bronwachtwoorden. De interface toont de laatste
+poging, laatste geslaagde poging en het tijdstip waarop gegevens zijn waargenomen.
+
+## Taakvoortgang en operationeel bewijs
+
+Een cursustag- of back-uptaak blijft op de server bestaan als je het tabblad sluit.
+De detail-URL bevat het taak-ID. Annuleren stopt toekomstig werk zodra dat veilig
+kan; reeds afgeronde pagina-updates blijven bewaard. Fouten en aantallen komen
+uit opgeslagen serverstatus. Zie [achtergrondtaken](background-jobs.md).
+
+Systeem- en deploymentinformatie tonen uitsluitend gemelde versie- en
+runtimegegevens. Ontbrekend bewijs blijft onbekend. Gebruik het broncommit, de
+image-identiteit en de toegepaste migraties samen wanneer je een release of
+herstel beoordeelt. Een gezonde applicatiecontrole bewijst op zichzelf niet dat
+de productieversie gelijk is aan de nieuwste repositoryversie.
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 944d57e..7ba4024 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -53,6 +53,9 @@ importers:
'@radix-ui/react-tooltip':
specifier: ^1.2.16
version: 1.2.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@tanstack/react-query':
+ specifier: ^5.104.0
+ version: 5.104.0(react@19.2.8)
'@tanstack/react-table':
specifier: ^8.21.3
version: 8.21.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
@@ -854,6 +857,14 @@ packages:
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
+ '@tanstack/query-core@5.104.0':
+ resolution: {integrity: sha512-JrC2r/JQlXt7khBSdUpsgxNvybzOg+aITa+ARRMlP2AFo93Y8vIqz077rp+e61mJetSZ0T6AJbwW1JHer1vrPQ==}
+
+ '@tanstack/react-query@5.104.0':
+ resolution: {integrity: sha512-e1TZmDCQnWIfiDVryIHeA6Idj+Lfx1hORLOhXS/l5wcgvtVD4yYqbTDl378sGQKIi2cSgb0TEMC9cKK8GGoJEw==}
+ peerDependencies:
+ react: ^18 || ^19
+
'@tanstack/react-table@8.21.3':
resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==}
engines: {node: '>=12'}
@@ -1259,6 +1270,7 @@ packages:
eslint@9.39.5:
resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
hasBin: true
peerDependencies:
jiti: '*'
@@ -2829,6 +2841,13 @@ snapshots:
'@standard-schema/spec@1.1.0': {}
+ '@tanstack/query-core@5.104.0': {}
+
+ '@tanstack/react-query@5.104.0(react@19.2.8)':
+ dependencies:
+ '@tanstack/query-core': 5.104.0
+ react: 19.2.8
+
'@tanstack/react-table@8.21.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
'@tanstack/table-core': 8.21.3
diff --git a/scripts/check-openapi-coverage.mjs b/scripts/check-openapi-coverage.mjs
index 9949ae2..e7684b2 100644
--- a/scripts/check-openapi-coverage.mjs
+++ b/scripts/check-openapi-coverage.mjs
@@ -15,6 +15,7 @@ import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import yaml from "js-yaml";
+import { extractRoutesFromSource } from "./lib/openapi-routes.mjs";
const endpointsDir = "src/backend/ItWorx.KnowledgeHub.Api/Endpoints";
const openApiPath = "docs/api/openapi.yaml";
@@ -32,42 +33,7 @@ function listEndpointFiles(dir) {
}
function extractRoutesFromFile(filePath) {
- const source = fs.readFileSync(filePath, "utf8");
- const routes = [];
-
- // MapGroup("/api/v1/xyz") establishes a prefix for every Map* call chained off the
- // returned variable within the same file — every endpoint file in this codebase uses
- // exactly this one-group-per-file pattern (verified by inspection), so a single
- // "most recent MapGroup literal in this file" tracking variable is sufficient and
- // avoids needing a real C# parser.
- const groupMatch = source.match(/MapGroup\(\s*"([^"]+)"\s*\)/);
- const groupPrefix = groupMatch ? groupMatch[1] : "";
-
- const mapCallPattern = /\.Map(Get|Post|Put|Delete|Patch)\(\s*"([^"]*)"/g;
- let match;
- while ((match = mapCallPattern.exec(source)) !== null) {
- const method = match[1].toUpperCase();
- const routeLiteral = match[2];
- const fullPath = groupPrefix + routeLiteral;
- routes.push({ method, path: normalizeRoute(fullPath), file: filePath });
- }
-
- return routes;
-}
-
-// Normalizes an ASP.NET route template's typed route constraints
-// (`{attachmentId:guid}`) to OpenAPI's untyped `{attachmentId}` style, and strips the
-// leading `/api/v1` prefix to match how docs/api/openapi.yaml keys its paths (relative
-// to the documented `servers[0].url`).
-function normalizeRoute(route) {
- let normalized = route.replace(/\{([a-zA-Z0-9_]+)(:[a-zA-Z]+)?\}/g, "{$1}");
- if (normalized.startsWith("/api/v1")) {
- normalized = normalized.slice("/api/v1".length);
- }
- if (normalized === "") {
- normalized = "/";
- }
- return normalized;
+ return extractRoutesFromSource(fs.readFileSync(filePath, "utf8"), filePath);
}
function isExcluded(routePath) {
diff --git a/scripts/generate-release-manifest.mjs b/scripts/generate-release-manifest.mjs
index 1a55070..b5b8606 100644
--- a/scripts/generate-release-manifest.mjs
+++ b/scripts/generate-release-manifest.mjs
@@ -24,6 +24,12 @@ import { spawnSync } from "node:child_process";
const repoRoot = process.cwd();
const outputPath = process.argv[2] ?? path.join(repoRoot, "release-manifest.json");
+const semverIdentifier = "(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)";
+const semverPattern = new RegExp(
+ `^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)` +
+ `(?:-(${semverIdentifier}(?:\\.${semverIdentifier})*))?` +
+ `(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$`
+);
function sha256File(relativePath) {
const fullPath = path.join(repoRoot, relativePath);
@@ -88,6 +94,18 @@ function readOpenApiVersion() {
}
function readSemanticVersion() {
+ const releaseTag = nonEmpty(process.env.RELEASE_TAG);
+ if (releaseTag) {
+ const version = releaseTag.startsWith("v") ? releaseTag.slice(1) : "";
+ if (!version || !semverPattern.test(version)) {
+ throw new Error(`RELEASE_TAG must be a v-prefixed semantic version; received '${releaseTag}'.`);
+ }
+ // Docker's web-build stage intentionally has no .git directory. A validated
+ // release tag is therefore the authoritative semantic version for tagged
+ // images, while the full tag remains separately recorded as releaseTag.
+ return version;
+ }
+
const versionFile = path.join(repoRoot, "VERSION");
if (fs.existsSync(versionFile)) {
return fs.readFileSync(versionFile, "utf8").trim();
diff --git a/scripts/lib/openapi-routes.mjs b/scripts/lib/openapi-routes.mjs
new file mode 100644
index 0000000..4655208
--- /dev/null
+++ b/scripts/lib/openapi-routes.mjs
@@ -0,0 +1,23 @@
+/** Extract literal Minimal API routes and their receiver-specific MapGroup prefix. */
+export function extractRoutesFromSource(source, file) {
+ const prefixes = new Map([["app", ""]]);
+ const groupPattern = /\b(?:var|const|RouteGroupBuilder)\s+(\w+)\s*=\s*(\w+)\.MapGroup\(\s*"([^"]+)"\s*\)/g;
+ for (const match of source.matchAll(groupPattern)) {
+ const parent = prefixes.get(match[2]);
+ if (parent === undefined) throw new Error(`Unknown MapGroup receiver ${match[2]} in ${file}`);
+ prefixes.set(match[1], parent + match[3]);
+ }
+ const routes = [];
+ const mapPattern = /\b(\w+)\.Map(Get|Post|Put|Delete|Patch)\(\s*"([^"]*)"/g;
+ for (const match of source.matchAll(mapPattern)) {
+ const prefix = prefixes.get(match[1]);
+ if (prefix === undefined) throw new Error(`Unknown route receiver ${match[1]} in ${file}`);
+ const path = (prefix + match[3]).replace(/\{([a-zA-Z0-9_]+)(:[a-zA-Z]+)?\}/g, "{$1}");
+ if (!path.startsWith("/api/v1")) {
+ if (!path.startsWith("/health")) throw new Error(`Unexpected non-API route ${path} in ${file}`);
+ continue;
+ }
+ routes.push({ method: match[2].toUpperCase(), path: path.slice("/api/v1".length) || "/", file });
+ }
+ return routes;
+}
diff --git a/scripts/lib/product-backlog.mjs b/scripts/lib/product-backlog.mjs
new file mode 100644
index 0000000..1fdb12b
--- /dev/null
+++ b/scripts/lib/product-backlog.mjs
@@ -0,0 +1,50 @@
+export function validateProductBacklog(backlog, plan) {
+ const errors = [];
+ if (backlog?.schemaVersion !== 2) errors.push("Product backlog schemaVersion must be 2.");
+ if (!Array.isArray(backlog?.statusValues)) errors.push("Product backlog statusValues must be an array.");
+ if (!Array.isArray(backlog?.items)) {
+ errors.push("Product backlog items must be an array.");
+ return errors;
+ }
+ const milestoneById = new Map((plan?.milestones ?? []).map((milestone) => [milestone.id, milestone]));
+ const itemIds = new Set();
+ const statuses = new Set(backlog.statusValues ?? []);
+ for (const item of backlog.items) {
+ if (!item || typeof item !== "object") {
+ errors.push("Product backlog contains an invalid item.");
+ continue;
+ }
+ if (typeof item.id !== "string" || !item.id || itemIds.has(item.id)) {
+ errors.push(`Product backlog item has a missing or duplicate ID: ${item.id ?? "(missing)"}.`);
+ }
+ itemIds.add(item.id);
+ if (!statuses.has(item.status)) errors.push(`${item.id} has an invalid status: ${item.status}.`);
+ const milestone = milestoneById.get(item.milestone);
+ if (!milestone) {
+ errors.push(`${item.id} references an unknown milestone: ${item.milestone}.`);
+ continue;
+ }
+ if (item.status === "complete" && (!Array.isArray(item.evidence) || item.evidence.length === 0)) {
+ errors.push(`${item.id} is complete without evidence references.`);
+ }
+ if (item.status === "todo" && milestone.status === "complete" && item.postMilestone !== true) {
+ errors.push(`${item.id} is todo in a completed milestone without an explicit postMilestone marker.`);
+ }
+ if (item.status === "partial" && (!item.notes || !Array.isArray(item.evidence) || item.evidence.length === 0)) {
+ errors.push(`${item.id} is partial without notes and evidence references.`);
+ }
+ if (item.status === "blocked_external") {
+ const acceptance = milestone.acceptance?.find((criterion) => criterion.id === item.acceptance);
+ if (!item.externalOwner || !acceptance?.external || acceptance.met !== false) {
+ errors.push(`${item.id} must reference an unmet external acceptance criterion and owner.`);
+ }
+ }
+ if (item.status === "complete" && milestone.status !== "complete") {
+ const acceptance = milestone.acceptance?.find((criterion) => criterion.id === item.acceptance);
+ if (!acceptance || acceptance.met !== true) {
+ errors.push(`${item.id} completes an unfinished milestone without a met acceptance criterion.`);
+ }
+ }
+ }
+ return errors;
+}
diff --git a/scripts/lib/release-gates.mjs b/scripts/lib/release-gates.mjs
new file mode 100644
index 0000000..b2bc53a
--- /dev/null
+++ b/scripts/lib/release-gates.mjs
@@ -0,0 +1,8 @@
+import { commandExists } from "./exec.mjs";
+
+export function requireReleaseTool(command, exists = commandExists) {
+ if (!exists(command)) {
+ throw new Error(`${command} is required for the release gate but was not found on PATH.`);
+ }
+ return command;
+}
diff --git a/scripts/lib/route-map.mjs b/scripts/lib/route-map.mjs
new file mode 100644
index 0000000..770a65d
--- /dev/null
+++ b/scripts/lib/route-map.mjs
@@ -0,0 +1,234 @@
+function skipQuoted(source, start) {
+ const quote = source[start];
+ let escaped = false;
+ for (let index = start + 1; index < source.length; index += 1) {
+ const character = source[index];
+ if (escaped) {
+ escaped = false;
+ } else if (character === "\\") {
+ escaped = true;
+ } else if (character === quote) {
+ return index + 1;
+ }
+ }
+ return source.length;
+}
+
+function readRouteTag(source, start) {
+ const closing = source[start + 1] === "/";
+ const nameStart = start + (closing ? 2 : 1);
+ const nameMatch = /^[A-Za-z_$][\w$.-]*/u.exec(source.slice(nameStart));
+ if (!nameMatch || nameMatch[0] !== "Route") return null;
+
+ let curlyDepth = 0;
+ let quote = null;
+ let escaped = false;
+ for (let index = nameStart + nameMatch[0].length; index < source.length; index += 1) {
+ const character = source[index];
+ if (quote !== null) {
+ if (escaped) escaped = false;
+ else if (character === "\\") escaped = true;
+ else if (character === quote) quote = null;
+ continue;
+ }
+ if (character === "\"" || character === "'" || character === "`") {
+ quote = character;
+ } else if (character === "{") {
+ curlyDepth += 1;
+ } else if (character === "}" && curlyDepth > 0) {
+ curlyDepth -= 1;
+ } else if (character === ">" && curlyDepth === 0) {
+ const text = source.slice(nameStart + nameMatch[0].length, index);
+ return {
+ closing,
+ selfClosing: !closing && /\/\s*$/u.test(text),
+ attributes: closing ? "" : text.replace(/\/\s*$/u, ""),
+ end: index + 1
+ };
+ }
+ }
+ return { error: "AuthenticatedRoutes.tsx contains an unterminated tag." };
+}
+
+function findStaticPath(attributes) {
+ const pathAttribute = /(?:^|\s)path\s*=\s*(?:"([^"]*)"|'([^']*)')/u.exec(attributes);
+ if (pathAttribute) return { value: pathAttribute[1] ?? pathAttribute[2] };
+ if (/(?:^|\s)path\s*=\s*\{/u.test(attributes)) return { error: "dynamic" };
+ return null;
+}
+
+function routePath(parentPath, childPath) {
+ if (childPath === "*") return parentPath ? `${parentPath.replace(/\/$/u, "")}/*` : "*";
+ if (childPath.startsWith("/")) return childPath.replace(/\/{2,}/gu, "/");
+ const joined = `${parentPath || ""}/${childPath}`.replace(/\/{2,}/gu, "/");
+ return joined || "/";
+}
+
+function routeFeatures(attributes) {
+ const features = new Set();
+ for (const match of attributes.matchAll(/\bfeatures\s*\?\.\s*([A-Za-z_$][\w$]*)/gu)) {
+ features.add(match[1]);
+ }
+ return [...features].sort();
+}
+
+function extractRouteStructure(source) {
+ const errors = [];
+ const routeStack = [];
+ const records = [];
+
+ for (let index = 0; index < source.length;) {
+ if (source.startsWith("//", index)) {
+ const newline = source.indexOf("\n", index + 2);
+ index = newline < 0 ? source.length : newline + 1;
+ continue;
+ }
+ if (source.startsWith("/*", index)) {
+ const commentEnd = source.indexOf("*/", index + 2);
+ index = commentEnd < 0 ? source.length : commentEnd + 2;
+ continue;
+ }
+ if (source[index] === "\"" || source[index] === "'" || source[index] === "`") {
+ index = skipQuoted(source, index);
+ continue;
+ }
+
+ if (source[index] === "<" && (source[index + 1] === "/" || /[A-Za-z_$]/u.test(source[index + 1] ?? ""))) {
+ const tag = readRouteTag(source, index);
+ if (tag?.error) {
+ errors.push(tag.error);
+ break;
+ }
+ if (tag) {
+ if (tag.closing) {
+ if (routeStack.length === 0) errors.push("AuthenticatedRoutes.tsx closes a without a matching opening tag.");
+ else routeStack.pop();
+ } else {
+ const parent = routeStack.at(-1);
+ const parentPath = parent?.path ?? "";
+ const pathAttribute = findStaticPath(tag.attributes);
+ const hasIndex = /(?:^|\s)index(?:\s|=|$)/u.test(tag.attributes);
+ const localFeatures = routeFeatures(tag.attributes);
+ const features = [...new Set([...(parent?.features ?? []), ...localFeatures])].sort();
+ const administrator = Boolean(parent?.administrator)
+ || /\bsession\.role\s*===?\s*["']Administrator["']/u.test(tag.attributes);
+
+ if (pathAttribute?.error === "dynamic") {
+ errors.push("AuthenticatedRoutes.tsx uses a dynamic Route path; route-map validation requires a static path.");
+ }
+ if (hasIndex && pathAttribute) errors.push("AuthenticatedRoutes.tsx declares a Route with both index and path.");
+
+ let fullPath = null;
+ if (hasIndex) fullPath = parentPath || "/";
+ else if (pathAttribute && !pathAttribute.error) fullPath = routePath(parentPath, pathAttribute.value);
+
+ if (fullPath !== null) {
+ records.push({
+ path: fullPath,
+ featureGates: features,
+ auth: administrator ? "administrator" : "authenticated",
+ depth: routeStack.length,
+ parentPath
+ });
+ }
+
+ if (!tag.selfClosing) {
+ routeStack.push({
+ path: fullPath ?? parentPath,
+ features,
+ administrator
+ });
+ }
+ }
+ index = tag.end;
+ continue;
+ }
+ }
+ index += 1;
+ }
+
+ if (routeStack.length > 0) errors.push("AuthenticatedRoutes.tsx contains an unclosed tag.");
+
+ const uniqueRecords = [];
+ for (const record of records) {
+ const priorIndex = uniqueRecords.findIndex((candidate) => candidate.path === record.path);
+ if (priorIndex < 0) {
+ uniqueRecords.push(record);
+ continue;
+ }
+
+ const prior = uniqueRecords[priorIndex];
+ const isNestedSamePath = record.parentPath === record.path && record.depth > prior.depth;
+ if (isNestedSamePath) {
+ uniqueRecords[priorIndex] = record;
+ } else {
+ errors.push(`AuthenticatedRoutes.tsx declares duplicate route path ${record.path}.`);
+ }
+ }
+
+ return { records: uniqueRecords, errors };
+}
+
+export function extractAuthenticatedRoutes(source) {
+ return extractRouteStructure(source).records;
+}
+
+export function extractAuthenticatedRoutePaths(source) {
+ return extractAuthenticatedRoutes(source).map((route) => route.path);
+}
+
+function mappedFeatureGates(route) {
+ if (Array.isArray(route?.featureGates)) return [...route.featureGates].sort();
+ return typeof route?.featureGate === "string" ? [route.featureGate] : [];
+}
+
+export function validateRouteMap(routeMap, authenticatedSource, sessionSource) {
+ const errors = [];
+ const { records, errors: routeErrors } = extractRouteStructure(authenticatedSource);
+ errors.push(...routeErrors);
+ const actualPaths = records.map((route) => route.path);
+ const mappedRoutes = Array.isArray(routeMap?.routes) ? routeMap.routes : [];
+ const mappedPaths = mappedRoutes.map((route) => route?.path);
+
+ if (new Set(mappedPaths).size !== mappedPaths.length) {
+ errors.push("product/route-map.json declares duplicate paths.");
+ }
+ const missing = actualPaths.filter((routePath) => !mappedPaths.includes(routePath));
+ const stale = mappedPaths.filter((routePath) => !actualPaths.includes(routePath));
+ if (missing.length > 0) errors.push(`Route map is missing: ${missing.join(", ")}.`);
+ if (stale.length > 0) errors.push(`Route map has stale entries: ${stale.join(", ")}.`);
+
+ if (routeMap?.sourceOfTruth !== "src/frontend/src/router/AuthenticatedRoutes.tsx") {
+ errors.push("Route map sourceOfTruth must identify AuthenticatedRoutes.tsx.");
+ }
+ const states = routeMap?.sessionStates;
+ const expectedStates = ["loading", "error", "setupRequired", "unauthenticated"];
+ if (!states || typeof states !== "object" || Array.isArray(states)) {
+ errors.push("Route map must document the pre-route session states.");
+ } else {
+ const actualStates = Object.keys(states).sort();
+ if (JSON.stringify(actualStates) !== JSON.stringify([...expectedStates].sort())) {
+ errors.push(`Route map session states must be ${expectedStates.join(", ")}.`);
+ }
+ for (const [condition, screen] of Object.entries(states)) {
+ if (typeof screen !== "string" || !sessionSource.includes(`<${screen}`)) {
+ errors.push(`Session screen is not present in App.tsx: ${condition}.`);
+ }
+ }
+ }
+
+ for (const actualRoute of records) {
+ const mappedRoute = mappedRoutes.find((route) => route?.path === actualRoute.path);
+ if (!mappedRoute) continue;
+ if (mappedRoute.auth !== actualRoute.auth) {
+ errors.push(`${actualRoute.path} must be marked ${actualRoute.auth}-only.`);
+ }
+ const expectedFeatures = actualRoute.featureGates;
+ const mappedFeatures = mappedFeatureGates(mappedRoute);
+ if (JSON.stringify(expectedFeatures) !== JSON.stringify(mappedFeatures)) {
+ const expected = expectedFeatures.length > 0 ? expectedFeatures.join(", ") : "none";
+ errors.push(`${actualRoute.path} feature-gate registry must be ${expected}.`);
+ }
+ }
+ return errors;
+}
diff --git a/scripts/validate-public-source.mjs b/scripts/validate-public-source.mjs
index 49b3ea4..50cb4de 100644
--- a/scripts/validate-public-source.mjs
+++ b/scripts/validate-public-source.mjs
@@ -28,4 +28,49 @@ if (!Array.isArray(manifest.files) || manifest.files.length === 0) {
process.exit(1);
}
-console.log(`Public source structure passed (${manifest.files.length} allowlisted files).`);
+const markdownFiles = [];
+function walkMarkdown(directory) {
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
+ if ([".git", "node_modules", "__pycache__"].includes(entry.name)) continue;
+ const fullPath = path.join(directory, entry.name);
+ if (entry.isDirectory()) walkMarkdown(fullPath);
+ else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) markdownFiles.push(fullPath);
+ }
+}
+
+function stripCode(markdown) {
+ const withoutFences = markdown.replace(/```[\s\S]*?```|~~~[\s\S]*?~~~/g, (block) =>
+ block.replace(/[^\n]/g, " "));
+ return withoutFences.replace(/`[^`\n]*`/g, (span) => " ".repeat(span.length));
+}
+
+walkMarkdown(process.cwd());
+const errors = [];
+const linkPattern = /\[[^\]]+\]\(([^)]+)\)/g;
+for (const markdownFile of markdownFiles) {
+ const content = stripCode(fs.readFileSync(markdownFile, "utf8"));
+ for (const match of content.matchAll(linkPattern)) {
+ let target = match[1].trim();
+ if (target.startsWith("#") || /^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith("//")) continue;
+ target = target.split("#", 1)[0].split("?", 1)[0];
+ if (!target) continue;
+ try {
+ target = decodeURIComponent(target);
+ } catch {
+ errors.push(`Could not URL-decode link in ${path.relative(process.cwd(), markdownFile)}: ${target}`);
+ continue;
+ }
+ const resolved = path.resolve(path.dirname(markdownFile), target);
+ if (!fs.existsSync(resolved)) {
+ errors.push(`Broken local Markdown link in ${path.relative(process.cwd(), markdownFile)}: ${match[1]}`);
+ }
+ }
+}
+
+if (errors.length > 0) {
+ console.error("Public source validation failed:");
+ for (const error of errors) console.error(`- ${error}`);
+ process.exit(1);
+}
+
+console.log(`Public source structure passed (${manifest.files.length} allowlisted files, ${markdownFiles.length} Markdown files, local links resolved).`);
diff --git a/scripts/verify.mjs b/scripts/verify.mjs
index da33f07..fb274fe 100644
--- a/scripts/verify.mjs
+++ b/scripts/verify.mjs
@@ -3,7 +3,8 @@
import fs from "node:fs";
import process from "node:process";
import { spawnSync } from "node:child_process";
-import { commandExists, run } from "./lib/exec.mjs";
+import { capture, commandExists, run } from "./lib/exec.mjs";
+import { requireReleaseTool } from "./lib/release-gates.mjs";
const flags = new Set(process.argv.slice(2));
const structureOnly = flags.has("--structure-only");
@@ -11,6 +12,24 @@ const fast = flags.has("--fast");
const ci = flags.has("--ci");
const release = flags.has("--release");
const publicSourceExport = fs.existsSync("PUBLIC_SOURCE_EXPORT.md");
+const releaseImageReference = process.env.RELEASE_IMAGE_REFERENCE ?? "itworx/knowledge-hub:local";
+let releaseImageId = null;
+// Test hosts use fixed configuration and do not need appsettings hot reload.
+// Avoid consuming the shared Linux host's inotify quota for hundreds of factories.
+const backendTestEnvironment = { DOTNET_HOSTBUILDER__RELOADCONFIGONCHANGE: "false" };
+
+function splitImageReference(reference) {
+ const tagSeparator = reference.lastIndexOf(":");
+ if (tagSeparator <= reference.lastIndexOf("/") || tagSeparator === 0 || /\s/.test(reference)) {
+ throw new Error(`Release image reference must include a repository and tag: '${reference}'.`);
+ }
+ const repository = reference.slice(0, tagSeparator);
+ const tag = reference.slice(tagSeparator + 1);
+ if (!repository || !/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/.test(tag)) {
+ throw new Error(`Release image reference has an invalid repository or tag: '${reference}'.`);
+ }
+ return { repository, tag };
+}
function findWorkingPython() {
// On some Windows setups `python3` resolves via PATH to the Microsoft Store
@@ -39,12 +58,22 @@ async function section(name, action) {
}
try {
+ // Fail before starting a long release matrix when a mandatory scanner is
+ // unavailable. CI provides these through the pinned release workflow setup;
+ // local release verification must install the same tools explicitly.
+ const releaseGitleaks = release ? requireReleaseTool("gitleaks") : null;
+ const releaseTrivy = release ? requireReleaseTool("trivy") : null;
+
await section("Bundle structure", async () => {
if (publicSourceExport) {
await run(process.execPath, ["scripts/validate-public-source.mjs"]);
} else {
await run(process.execPath, ["scripts/validate-bundle.mjs"]);
await run(process.execPath, ["scripts/test-safety-hook.mjs"]);
+ await run(process.execPath, ["scripts/validate-product-state.mjs"]);
+ await run(process.execPath, ["scripts/validate-route-map.mjs"]);
+ await run(process.execPath, ["--test", "scripts/tests/route-map.test.mjs", "scripts/tests/product-backlog.test.mjs", "scripts/tests/validate-public-source.test.mjs", "scripts/tests/validate-bundle.test.mjs", "scripts/tests/v2-r4-real-backup-restore-drill.test.mjs", "scripts/tests/cleanup-temporary-resources.test.mjs"]);
+ await run(python, ["-m", "unittest", "scripts.tests.test_public_source_pipeline"]);
}
});
@@ -108,7 +137,7 @@ try {
"trx",
"--results-directory",
"artifacts/test-results"
- ]);
+ ], { env: backendTestEnvironment });
});
await section("Frontend install and verification", async () => {
@@ -149,16 +178,60 @@ try {
if (ci || release) {
await section("Production image build", async () => {
- await run("docker", ["compose", "build", "app"], {
- env: {
- POSTGRES_PASSWORD: "verification-only-not-for-production",
- BUILD_COMMIT: process.env.GITHUB_SHA ?? process.env.CI_COMMIT_SHA ?? "verification"
+ const buildCommit = process.env.GITHUB_SHA ?? process.env.CI_COMMIT_SHA ?? "verification";
+ if (release) {
+ const { repository, tag } = splitImageReference(releaseImageReference);
+ await run("docker", [
+ "build",
+ "--file",
+ "deploy/docker/Dockerfile",
+ "--tag",
+ releaseImageReference,
+ "--build-arg",
+ `BUILD_COMMIT=${buildCommit}`,
+ "--build-arg",
+ `RELEASE_TAG=${process.env.RELEASE_TAG ?? ""}`,
+ "--build-arg",
+ `IMAGE_REPOSITORY=${repository}`,
+ "--build-arg",
+ `IMAGE_TAG=${tag}`,
+ "."
+ ]);
+ releaseImageId = await capture("docker", [
+ "image",
+ "inspect",
+ "--format",
+ "{{.Id}}",
+ releaseImageReference
+ ]);
+ if (!/^sha256:[0-9a-f]{64}$/i.test(releaseImageId)) {
+ throw new Error(`Docker returned an invalid image ID for '${releaseImageReference}'.`);
}
- });
+ const imageIdPath = "artifacts/release-verification/image-id.txt";
+ fs.mkdirSync("artifacts/release-verification", { recursive: true });
+ fs.writeFileSync(imageIdPath, `${releaseImageId}\n`, "utf8");
+ console.log(`Captured immutable release image ID in ${imageIdPath}.`);
+ } else {
+ await run("docker", ["compose", "build", "app"], {
+ env: {
+ POSTGRES_PASSWORD: "verification-only-not-for-production",
+ BUILD_COMMIT: buildCommit
+ }
+ });
+ }
});
}
if (release) {
+ await section("Release evidence integrity tests", async () => {
+ await run(process.execPath, [
+ "--test",
+ "scripts/tests/release-gates.test.mjs",
+ "scripts/tests/generate-release-evidence.test.mjs",
+ "scripts/tests/generate-release-manifest.test.mjs"
+ ]);
+ });
+
await section("Full-scale search/tree suite (release-gate only)", async () => {
await run("dotnet", [
"test",
@@ -167,8 +240,12 @@ try {
"Release",
"--no-build",
"--filter",
- "Category=ReleaseScale"
- ]);
+ "Category=ReleaseScale",
+ "--logger",
+ "trx",
+ "--results-directory",
+ "artifacts/test-results"
+ ], { env: backendTestEnvironment });
});
await section(".NET dependency vulnerability scan", async () => {
@@ -195,32 +272,24 @@ try {
});
await section("Secret scan", async () => {
- if (!commandExists("gitleaks")) {
- throw new Error("gitleaks is required for the release secret scan but was not found on PATH.");
- }
-
- await run("gitleaks", ["detect", "--source", ".", "--redact"]);
+ await run(releaseGitleaks, ["detect", "--source", ".", "--redact"]);
});
- if (commandExists("trivy")) {
- await section("Container vulnerability scan", async () => {
- await run("trivy", [
- "image",
- "--severity",
- "CRITICAL,HIGH",
- "--exit-code",
- "1",
- "--scanners",
- "vuln",
- "itworx/knowledge-hub:local"
- ]);
- });
- } else {
- console.warn(
- "\ntrivy not found on PATH — container vulnerability scan skipped. Install " +
- "trivy before a real release; this is not a substitute for the scan."
- );
- }
+ await section("Container vulnerability scan", async () => {
+ if (!releaseImageId) {
+ throw new Error("The release image ID was not captured after the production image build.");
+ }
+ await run(releaseTrivy, [
+ "image",
+ "--severity",
+ "CRITICAL,HIGH",
+ "--exit-code",
+ "1",
+ "--scanners",
+ "vuln",
+ releaseImageId
+ ]);
+ });
console.warn(
"\nRelease verification still requires the milestone-defined E2E (desktop + " +
diff --git a/src/backend/ItWorx.KnowledgeHub.Api/BackgroundServices/JobWorker.cs b/src/backend/ItWorx.KnowledgeHub.Api/BackgroundServices/JobWorker.cs
new file mode 100644
index 0000000..c9aa60c
--- /dev/null
+++ b/src/backend/ItWorx.KnowledgeHub.Api/BackgroundServices/JobWorker.cs
@@ -0,0 +1,461 @@
+using System.Text.Json;
+using ItWorx.KnowledgeHub.Application.Identity;
+using ItWorx.KnowledgeHub.Application.Jobs;
+using ItWorx.KnowledgeHub.Application.Jobs.UseCases;
+using ItWorx.KnowledgeHub.Application.Knowledge;
+using ItWorx.KnowledgeHub.Application.Knowledge.UseCases;
+using ItWorx.KnowledgeHub.Application.Operations;
+using ItWorx.KnowledgeHub.Application.Operations.UseCases;
+using ItWorx.KnowledgeHub.Domain.Identity;
+using ItWorx.KnowledgeHub.Domain.Jobs;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace ItWorx.KnowledgeHub.Api.BackgroundServices;
+
+/// Executes durable jobs with bounded worker concurrency. PostgreSQL leases
+/// allow another process to resume work after a crash; each course page is handled as
+/// an independent, idempotent ETag-guarded update.
+public sealed partial class JobWorker(
+ IServiceScopeFactory scopeFactory,
+ BackgroundJobsOptions options,
+ TimeProvider timeProvider,
+ PlatformMetrics metrics,
+ ILogger logger) : BackgroundService
+{
+ private long lastMetricsRefreshMilliseconds;
+ private int metricsRefreshInProgress;
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ options.Validate();
+ if (!options.Enabled)
+ {
+ LogDisabled();
+ return;
+ }
+ var workers = Enumerable.Range(0, options.MaxConcurrency)
+ .Select(_ => RunWorkerAsync(stoppingToken));
+ await Task.WhenAll(workers);
+ }
+
+ private async Task RunWorkerAsync(CancellationToken stoppingToken)
+ {
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ using var scope = scopeFactory.CreateScope();
+ var repository = scope.ServiceProvider.GetRequiredService();
+ var leaseToken = Guid.NewGuid();
+ Guid? jobId;
+ try
+ {
+ var now = timeProvider.GetUtcNow();
+ jobId = await repository.TryClaimNextAsync(
+ leaseToken, now, now.AddSeconds(options.LeaseSeconds), stoppingToken);
+ await RefreshMetricsAsync(repository, force: jobId is not null, stoppingToken);
+ }
+ catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
+ {
+ break;
+ }
+ catch (Exception exception)
+ {
+ LogWorkerPollFailed(exception.GetType().Name);
+ try { await Task.Delay(TimeSpan.FromMilliseconds(options.PollMilliseconds), timeProvider, stoppingToken); }
+ catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { break; }
+ continue;
+ }
+
+ if (jobId is null)
+ {
+ try { await Task.Delay(TimeSpan.FromMilliseconds(options.PollMilliseconds), timeProvider, stoppingToken); }
+ catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { break; }
+ continue;
+ }
+
+ var claimedJob = await repository.FindLeasedAsync(jobId.Value, leaseToken, stoppingToken);
+ if (claimedJob is null)
+ continue;
+
+ using var leaseLost = new CancellationTokenSource();
+ using var userCancellation = new CancellationTokenSource();
+ using var operationCancellation = CancellationTokenSource.CreateLinkedTokenSource(
+ stoppingToken, leaseLost.Token);
+ using var monitorCancellation = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken);
+ var monitor = MonitorLeaseAsync(
+ jobId.Value, leaseToken, leaseLost, userCancellation, monitorCancellation.Token);
+ var measurement = metrics.Start("job");
+
+ try
+ {
+ await ExecuteLeasedJobAsync(
+ scope.ServiceProvider, jobId.Value, leaseToken, operationCancellation.Token,
+ userCancellation.Token, stoppingToken);
+ var completed = await repository.FindAsync(claimedJob.WorkspaceId, jobId.Value, stoppingToken);
+ if (completed?.Status == BackgroundJobStatus.Cancelled)
+ measurement.Cancel();
+ else if (completed?.Status == BackgroundJobStatus.Succeeded)
+ measurement.Complete(completed.Processed);
+ }
+ catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
+ {
+ // Leave the durable running row leased. A different worker will recover
+ // it when the lease expires; explicit user cancellation is checked below.
+ if (userCancellation.IsCancellationRequested && !leaseLost.IsCancellationRequested)
+ {
+ try
+ {
+ await repository.CancelLeasedJobAsync(jobId.Value, leaseToken, timeProvider.GetUtcNow(), CancellationToken.None);
+ measurement.Cancel();
+ }
+ catch (Exception exception)
+ {
+ LogJobStateWriteFailed(jobId.Value, exception.GetType().Name);
+ }
+ }
+ }
+ catch (OperationCanceledException) when (userCancellation.IsCancellationRequested && !leaseLost.IsCancellationRequested)
+ {
+ try
+ {
+ await repository.CancelLeasedJobAsync(jobId.Value, leaseToken, timeProvider.GetUtcNow(), CancellationToken.None);
+ measurement.Cancel();
+ }
+ catch (Exception exception)
+ {
+ LogJobStateWriteFailed(jobId.Value, exception.GetType().Name);
+ }
+ }
+ catch (OperationCanceledException) when (leaseLost.IsCancellationRequested)
+ {
+ LogLeaseLost(jobId.Value);
+ }
+ catch (Exception exception)
+ {
+ var retry = await RetryTimeAsync(repository, jobId.Value, leaseToken, CancellationToken.None);
+ try
+ {
+ await repository.RetryLeasedJobAsync(
+ jobId.Value, leaseToken, timeProvider.GetUtcNow(), retry,
+ "processing_retry", "De tijdelijke verwerking is mislukt; de taak wordt opnieuw geprobeerd.",
+ CancellationToken.None);
+ }
+ catch (Exception stateException)
+ {
+ LogJobStateWriteFailed(jobId.Value, stateException.GetType().Name);
+ }
+ LogJobExecutionFailed(jobId.Value, exception.GetType().Name);
+ }
+ finally
+ {
+ measurement.Dispose();
+ monitorCancellation.Cancel();
+ try { await monitor; }
+ catch (OperationCanceledException) { }
+ }
+
+ await RefreshMetricsAsync(repository, force: true, stoppingToken);
+ }
+ }
+
+ private async Task ExecuteLeasedJobAsync(
+ IServiceProvider services,
+ Guid jobId,
+ Guid leaseToken,
+ CancellationToken operationToken,
+ CancellationToken userCancellationToken,
+ CancellationToken hostToken)
+ {
+ var jobs = services.GetRequiredService();
+ var job = await jobs.FindLeasedAsync(jobId, leaseToken, operationToken);
+ if (job is null)
+ return;
+
+ if (!await IsActorAdministratorAsync(services, job, operationToken))
+ {
+ await jobs.FailLeasedJobAsync(jobId, leaseToken, timeProvider.GetUtcNow(),
+ "actor_unavailable", "De uitvoerder is niet langer actief als beheerder.", operationToken);
+ return;
+ }
+
+ if (await jobs.IsCancellationRequestedAsync(jobId, leaseToken, operationToken))
+ {
+ await jobs.CancelLeasedJobAsync(jobId, leaseToken, timeProvider.GetUtcNow(), operationToken);
+ return;
+ }
+
+ if (job.Type == BackgroundJobType.Backup)
+ {
+ using var backupCancellation = CancellationTokenSource.CreateLinkedTokenSource(operationToken, userCancellationToken);
+ await ExecuteBackupAsync(services, job, leaseToken, backupCancellation.Token, operationToken);
+ return;
+ }
+
+ if (job.Type == BackgroundJobType.CourseTagging)
+ await ExecuteCourseTaggingAsync(services, job, leaseToken, operationToken);
+ }
+
+ private async Task ExecuteBackupAsync(
+ IServiceProvider services,
+ BackgroundJob job,
+ Guid leaseToken,
+ CancellationToken backupCancellationToken,
+ CancellationToken stateCancellationToken)
+ {
+ var jobs = services.GetRequiredService();
+ var backups = services.GetRequiredService();
+ var summary = await backups.ExecuteAsync(
+ new CreateBackupCommand(job.ActorId, job.CorrelationId), backupCancellationToken);
+ var result = JsonSerializer.Serialize(new
+ {
+ backupId = summary.BackupId,
+ createdAt = summary.CreatedAt,
+ sizeBytes = summary.TotalSizeBytes,
+ tableCount = summary.TableCount,
+ attachmentCount = summary.AttachmentCount,
+ schemaVersion = summary.SchemaVersion
+ });
+ await jobs.RecordJobOutcomeAsync(
+ job.Id, leaseToken, BackgroundJobItemState.Succeeded, null, null,
+ stateCancellationToken);
+ await jobs.CompleteLeasedJobAsync(job.Id, leaseToken, result, timeProvider.GetUtcNow(), stateCancellationToken);
+ }
+
+ private async Task ExecuteCourseTaggingAsync(
+ IServiceProvider services,
+ BackgroundJob job,
+ Guid leaseToken,
+ CancellationToken operationToken)
+ {
+ using var payload = JsonDocument.Parse(job.PayloadJson);
+ var rootPageId = payload.RootElement.GetProperty("rootPageId").GetGuid();
+ var tag = payload.RootElement.GetProperty("tag").GetString()!;
+ var jobs = services.GetRequiredService();
+ var pages = services.GetRequiredService();
+ var updatePage = services.GetRequiredService();
+
+ while (true)
+ {
+ operationToken.ThrowIfCancellationRequested();
+ if (await jobs.IsCancellationRequestedAsync(job.Id, leaseToken, operationToken))
+ {
+ await jobs.CancelLeasedJobAsync(job.Id, leaseToken, timeProvider.GetUtcNow(), operationToken);
+ return;
+ }
+
+ var currentJob = await jobs.FindLeasedAsync(job.Id, leaseToken, operationToken);
+ if (currentJob is null)
+ return;
+ if (currentJob.Processed >= currentJob.Total)
+ {
+ await jobs.CompleteLeasedJobAsync(job.Id, leaseToken, JsonSerializer.Serialize(new
+ {
+ rootPageId,
+ tag,
+ total = currentJob.Total,
+ processed = currentJob.Processed,
+ succeeded = currentJob.Succeeded,
+ skipped = currentJob.Skipped,
+ failed = currentJob.Failed
+ }), timeProvider.GetUtcNow(), operationToken);
+ return;
+ }
+
+ if (!await IsActorAdministratorAsync(services, currentJob, operationToken))
+ {
+ await jobs.FailLeasedJobAsync(job.Id, leaseToken, timeProvider.GetUtcNow(),
+ "actor_unavailable", "De uitvoerder is niet langer actief als beheerder.", operationToken);
+ return;
+ }
+
+ var item = await jobs.FindNextPageItemAsync(job.Id, leaseToken, operationToken);
+ if (item is null)
+ throw new InvalidOperationException("The job snapshot is incomplete.");
+
+ await jobs.MarkPageItemProcessingAsync(job.Id, leaseToken, item.Ordinal, timeProvider.GetUtcNow(), operationToken);
+ var page = await pages.FindAsync(item.PageId, operationToken);
+ if (page is null || page.WorkspaceId != job.WorkspaceId || page.IsTrashed)
+ {
+ await jobs.RecordPageItemOutcomeAsync(
+ job.Id, leaseToken, item.Ordinal, BackgroundJobItemState.Failed,
+ "page_unavailable", "Pagina is niet langer beschikbaar.", item.PageId,
+ timeProvider.GetUtcNow(), operationToken);
+ continue;
+ }
+
+ if (page.Tags.Any(existing => string.Equals(existing, tag, StringComparison.OrdinalIgnoreCase)))
+ {
+ await jobs.RecordPageItemOutcomeAsync(
+ job.Id, leaseToken, item.Ordinal,
+ item.InitiallyTagged ? BackgroundJobItemState.Skipped : BackgroundJobItemState.Succeeded,
+ null, null, null, timeProvider.GetUtcNow(), operationToken);
+ continue;
+ }
+
+ if (page.Tags.Count >= Domain.Knowledge.Page.MaxTagCount)
+ {
+ await jobs.RecordPageItemOutcomeAsync(
+ job.Id, leaseToken, item.Ordinal, BackgroundJobItemState.Skipped,
+ null, null, null, timeProvider.GetUtcNow(), operationToken);
+ continue;
+ }
+
+ var etag = await pages.GetETagAsync(page.Id, operationToken);
+ if (etag is null)
+ {
+ await jobs.RecordPageItemOutcomeAsync(
+ job.Id, leaseToken, item.Ordinal, BackgroundJobItemState.Failed,
+ "page_unavailable", "Pagina is niet langer beschikbaar.", page.Id,
+ timeProvider.GetUtcNow(), operationToken);
+ continue;
+ }
+
+ var tags = page.Tags.Append(tag).ToArray();
+ var result = await updatePage.ExecuteAsync(
+ new UpdatePageCommand(
+ page.Id,
+ page.TitleValue,
+ page.Markdown,
+ page.DocumentType.ToString(),
+ page.DocumentStatus.ToString(),
+ $"Cursustag {tag} toegevoegd",
+ tags,
+ etag,
+ job.ActorId,
+ job.CorrelationId),
+ operationToken);
+
+ if (result.Status == PageOperationStatus.Success)
+ {
+ await jobs.RecordPageItemOutcomeAsync(
+ job.Id, leaseToken, item.Ordinal, BackgroundJobItemState.Succeeded,
+ null, null, null, timeProvider.GetUtcNow(), operationToken);
+ }
+ else
+ {
+ var conflict = result.Status == PageOperationStatus.ConcurrencyConflict;
+ await jobs.RecordPageItemOutcomeAsync(
+ job.Id, leaseToken, item.Ordinal, BackgroundJobItemState.Failed,
+ conflict ? "concurrency_conflict" : "page_update_failed",
+ conflict ? "Pagina is tijdens de taak gewijzigd; de wijziging is overgeslagen."
+ : "De pagina kon niet worden bijgewerkt.",
+ page.Id, timeProvider.GetUtcNow(), operationToken);
+ }
+ }
+ }
+
+ private static async Task IsActorAdministratorAsync(
+ IServiceProvider services,
+ BackgroundJob job,
+ CancellationToken cancellationToken)
+ {
+ var accounts = services.GetRequiredService();
+ var memberships = services.GetRequiredService();
+ var account = await accounts.FindByIdAsync(job.ActorId, cancellationToken);
+ if (account is not { Enabled: true })
+ return false;
+ var role = await memberships.GetRoleAsync(job.WorkspaceId, job.ActorId, cancellationToken);
+ return role == WorkspaceRole.Administrator;
+ }
+
+ private async Task MonitorLeaseAsync(
+ Guid jobId,
+ Guid leaseToken,
+ CancellationTokenSource leaseLost,
+ CancellationTokenSource userCancellation,
+ CancellationToken cancellationToken)
+ {
+ var lastRenewal = timeProvider.GetUtcNow();
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ await Task.Delay(TimeSpan.FromSeconds(1), timeProvider, cancellationToken);
+ using var scope = scopeFactory.CreateScope();
+ var jobs = scope.ServiceProvider.GetRequiredService();
+ if (await jobs.IsCancellationRequestedAsync(jobId, leaseToken, cancellationToken))
+ userCancellation.Cancel();
+
+ var now = timeProvider.GetUtcNow();
+ if (now - lastRenewal >= TimeSpan.FromSeconds(options.LeaseRenewSeconds))
+ {
+ if (!await jobs.RenewLeaseAsync(jobId, leaseToken, now.AddSeconds(options.LeaseSeconds), cancellationToken))
+ {
+ leaseLost.Cancel();
+ return;
+ }
+ lastRenewal = now;
+ }
+ }
+ catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+ catch (Exception exception)
+ {
+ // A transient telemetry/lease read failure does not cancel work before
+ // the lease can expire. The database remains authoritative on writes.
+ LogLeaseMonitorFailed(jobId, exception.GetType().Name);
+ }
+ }
+ }
+
+ private async Task RefreshMetricsAsync(IBackgroundJobRepository repository, bool force, CancellationToken cancellationToken)
+ {
+ var nowMilliseconds = timeProvider.GetUtcNow().ToUnixTimeMilliseconds();
+ if (!force && nowMilliseconds - Interlocked.Read(ref lastMetricsRefreshMilliseconds) < 10_000)
+ return;
+ if (Interlocked.CompareExchange(ref metricsRefreshInProgress, 1, 0) != 0)
+ return;
+ try
+ {
+ var statistics = await repository.GetQueueStatisticsAsync(cancellationToken);
+ var oldestAge = statistics.OldestQueuedAt is { } oldest
+ ? Math.Max(0, (timeProvider.GetUtcNow() - oldest).TotalSeconds)
+ : (double?)null;
+ metrics.SetQueueState(statistics.Queued, statistics.Running, statistics.Failed, oldestAge);
+ Interlocked.Exchange(ref lastMetricsRefreshMilliseconds, nowMilliseconds);
+ }
+ catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
+ {
+ }
+ catch (Exception exception)
+ {
+ LogJobMetricsRefreshFailed(exception.GetType().Name);
+ }
+ finally
+ {
+ Volatile.Write(ref metricsRefreshInProgress, 0);
+ }
+ }
+
+ private async Task RetryTimeAsync(
+ IBackgroundJobRepository repository, Guid jobId, Guid leaseToken, CancellationToken cancellationToken)
+ {
+ var attempt = (await repository.FindLeasedAsync(jobId, leaseToken, cancellationToken))?.AttemptCount ?? 1;
+ var seconds = attempt switch { 1 => 2, 2 => 10, _ => 30 };
+ return timeProvider.GetUtcNow().AddSeconds(seconds);
+ }
+
+ [LoggerMessage(Level = LogLevel.Warning, Message = "Background job poll failed ({ErrorType}).")]
+ private partial void LogWorkerPollFailed(string errorType);
+
+ [LoggerMessage(Level = LogLevel.Warning, Message = "Background job {JobId} failed ({ErrorType}).")]
+ private partial void LogJobExecutionFailed(Guid jobId, string errorType);
+
+ [LoggerMessage(Level = LogLevel.Warning, Message = "Background job {JobId} lost its processing lease.")]
+ private partial void LogLeaseLost(Guid jobId);
+
+ [LoggerMessage(Level = LogLevel.Warning, Message = "Background job {JobId} state write failed ({ErrorType}).")]
+ private partial void LogJobStateWriteFailed(Guid jobId, string errorType);
+
+ [LoggerMessage(Level = LogLevel.Warning, Message = "Background job {JobId} lease monitoring failed ({ErrorType}).")]
+ private partial void LogLeaseMonitorFailed(Guid jobId, string errorType);
+
+ [LoggerMessage(Level = LogLevel.Warning, Message = "Background job metrics refresh failed ({ErrorType}).")]
+ private partial void LogJobMetricsRefreshFailed(string errorType);
+
+ [LoggerMessage(Level = LogLevel.Information, Message = "Background job worker is disabled by configuration.")]
+ private partial void LogDisabled();
+}
diff --git a/src/backend/ItWorx.KnowledgeHub.Api/BackgroundServices/OperationalTelemetryBackgroundService.cs b/src/backend/ItWorx.KnowledgeHub.Api/BackgroundServices/OperationalTelemetryBackgroundService.cs
new file mode 100644
index 0000000..5961646
--- /dev/null
+++ b/src/backend/ItWorx.KnowledgeHub.Api/BackgroundServices/OperationalTelemetryBackgroundService.cs
@@ -0,0 +1,41 @@
+using ItWorx.KnowledgeHub.Application.Operations;
+
+namespace ItWorx.KnowledgeHub.Api.BackgroundServices;
+
+public sealed partial class OperationalTelemetryBackgroundService(
+ IServiceScopeFactory scopeFactory, PlatformMetrics metrics, TimeProvider clock,
+ ILogger logger) : BackgroundService
+{
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ try
+ {
+ // Do not race initial database migration during host startup.
+ await Task.Delay(TimeSpan.FromSeconds(30), clock, stoppingToken);
+ using var scope = scopeFactory.CreateScope();
+ var snapshot = await scope.ServiceProvider.GetRequiredService()
+ .ReadAsync(stoppingToken);
+ var now = clock.GetUtcNow();
+ metrics.SetQueueState(snapshot.QueuedJobs, snapshot.RunningJobs, snapshot.FailedJobs,
+ Age(snapshot.OldestQueuedJobAt, now));
+ metrics.SetOperationalAges(Age(snapshot.OldestPendingIngestionAt, now),
+ Age(snapshot.LatestInventoryCaptureAt, now), Age(snapshot.LatestVerifiedBackupAt, now));
+ }
+ catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { break; }
+ catch (Exception)
+ {
+ // Omit stale gauges after a failed read, rather than exporting old success.
+ metrics.ClearSnapshots();
+ LogReadFailed();
+ }
+ }
+ }
+
+ private static double? Age(DateTimeOffset? value, DateTimeOffset now) =>
+ value is { } time && time <= now ? (now - time).TotalSeconds : null;
+
+ [LoggerMessage(Level = LogLevel.Warning, Message = "Operationele metriekmeting mislukt; waarden blijven onbekend tot de volgende geslaagde meting.")]
+ private partial void LogReadFailed();
+}
diff --git a/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/ActivityEndpoints.cs b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/ActivityEndpoints.cs
index faae588..77c02d3 100644
--- a/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/ActivityEndpoints.cs
+++ b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/ActivityEndpoints.cs
@@ -18,13 +18,14 @@ public static class ActivityEndpoints
}
private static async Task ListAsync(
- IAuditEventReader reader,
+ IActivityEventReader reader,
[FromQuery] int? limit,
+ [FromQuery] long? before,
CancellationToken cancellationToken)
{
- var items = await reader.ListRecentAsync(limit ?? 100, cancellationToken);
- return Results.Ok(new ActivityListResponse(items.Select(item => new ActivityItemResponse(
+ var page = await reader.ListAsync(limit ?? 100, before, cancellationToken);
+ return Results.Ok(new ActivityListResponse(page.Items.Select(item => new ActivityItemResponse(
item.Id, item.ActorKind, item.ActorId, item.Source, item.Action, item.TargetType,
- item.TargetId, item.Summary, item.OccurredAt)).ToArray()));
+ item.TargetId, item.Summary, item.OccurredAt)).ToArray(), page.NextCursor));
}
}
diff --git a/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/AdminAuditEndpoints.cs b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/AdminAuditEndpoints.cs
new file mode 100644
index 0000000..4c2eb5b
--- /dev/null
+++ b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/AdminAuditEndpoints.cs
@@ -0,0 +1,22 @@
+using ItWorx.KnowledgeHub.Api.Authorization;
+using ItWorx.KnowledgeHub.Application.Audit;
+using ItWorx.KnowledgeHub.Contracts.Activity;
+using Microsoft.AspNetCore.Mvc;
+
+namespace ItWorx.KnowledgeHub.Api.Endpoints;
+
+public static class AdminAuditEndpoints
+{
+ public static IEndpointRouteBuilder MapAdminAuditEndpoints(this IEndpointRouteBuilder app)
+ {
+ app.MapGet("/api/v1/admin/audit-log", async (
+ IAuditEventReader reader, [FromQuery] int? limit, [FromQuery] long? before, CancellationToken cancellationToken) =>
+ {
+ var page = await reader.ListPageAsync(limit ?? 50, before, cancellationToken);
+ return Results.Ok(new AdminAuditListResponse(page.Items.Select(item => new AdminAuditItemResponse(
+ item.Id, item.ActorKind, item.ActorId, item.Source, item.Action, item.TargetType,
+ item.TargetId, item.Summary, item.OccurredAt, item.CorrelationId)).ToArray(), page.NextCursor));
+ }).WithTags("Admin").RequireAuthorization(AuthorizationPolicies.RequireAdministrator);
+ return app;
+ }
+}
diff --git a/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/AuthEndpoints.cs b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/AuthEndpoints.cs
index c1b799f..4113904 100644
--- a/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/AuthEndpoints.cs
+++ b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/AuthEndpoints.cs
@@ -2,12 +2,12 @@ using ItWorx.KnowledgeHub.Api.Security;
using ItWorx.KnowledgeHub.Application.Identity;
using ItWorx.KnowledgeHub.Application.Identity.UseCases;
using ItWorx.KnowledgeHub.Application.Ingestion;
+using ItWorx.KnowledgeHub.Application.Platform;
using ItWorx.KnowledgeHub.Contracts.Auth;
using ItWorx.KnowledgeHub.Contracts.Errors;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Options;
namespace ItWorx.KnowledgeHub.Api.Endpoints;
@@ -40,18 +40,19 @@ public static class AuthEndpoints
private static async Task> GetSessionAsync(
GetSessionUseCase useCase,
- IOptions ingestionOptions,
+ IPlatformSettingsReader platformSettings,
CancellationToken cancellationToken)
{
var session = await useCase.ExecuteAsync(cancellationToken);
- return TypedResults.Ok(ToSessionResponse(session, ingestionOptions.Value));
+ var ingestion = (await platformSettings.GetEffectiveAsync(cancellationToken)).Values.IngestionProposals;
+ return TypedResults.Ok(ToSessionResponse(session, ingestion));
}
private static async Task, ProblemHttpResult>> LoginAsync(
[FromBody] LoginRequest request,
LoginUseCase loginUseCase,
GetSessionUseCase sessionUseCase,
- IOptions ingestionOptions,
+ IPlatformSettingsReader platformSettings,
HttpContext httpContext,
CancellationToken cancellationToken)
{
@@ -65,7 +66,8 @@ public static class AuthEndpoints
{
case PasswordSignInStatus.Success:
var session = await sessionUseCase.ExecuteAsync(cancellationToken);
- return TypedResults.Ok(ToSessionResponse(session, ingestionOptions.Value));
+ var ingestion = (await platformSettings.GetEffectiveAsync(cancellationToken)).Values.IngestionProposals;
+ return TypedResults.Ok(ToSessionResponse(session, ingestion));
case PasswordSignInStatus.LockedOut:
return TypedResults.Problem(
@@ -124,7 +126,7 @@ public static class AuthEndpoints
};
}
- private static SessionResponse ToSessionResponse(SessionInfo session, IngestionProposalOptions ingestionOptions) => new(
+ private static SessionResponse ToSessionResponse(SessionInfo session, ItWorx.KnowledgeHub.Domain.Platform.IngestionProposalPolicyValues ingestionOptions) => new(
session.Authenticated,
session.SetupRequired,
session.UserId,
diff --git a/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/BackupsEndpoints.cs b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/BackupsEndpoints.cs
index d15438c..fbe4222 100644
--- a/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/BackupsEndpoints.cs
+++ b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/BackupsEndpoints.cs
@@ -1,8 +1,11 @@
+using System.Globalization;
using System.Security.Claims;
using ItWorx.KnowledgeHub.Api.Authorization;
using ItWorx.KnowledgeHub.Api.Security;
+using ItWorx.KnowledgeHub.Application.Audit;
using ItWorx.KnowledgeHub.Application.Operations;
using ItWorx.KnowledgeHub.Application.Operations.UseCases;
+using ItWorx.KnowledgeHub.Application.Platform;
using ItWorx.KnowledgeHub.Contracts.Operations;
using Microsoft.AspNetCore.Http.HttpResults;
@@ -22,31 +25,93 @@ public static class BackupsEndpoints
.RequireAuthorization(AuthorizationPolicies.RequireAdministrator);
group.MapGet("", ListAsync);
+ group.MapGet("/status", StatusAsync);
+ group.MapPut("/{backupId}/protection", ProtectAsync)
+ .AddEndpointFilter();
group.MapPost("", CreateAsync)
+ .RequireRateLimiting(RateLimitPolicies.JobCreation)
.AddEndpointFilter();
return app;
}
- private static async Task> ListAsync(ListBackupsUseCase useCase, CancellationToken cancellationToken)
+ private static async Task> ListAsync(ListBackupsUseCase useCase, IBackupEvidenceStore evidence, CancellationToken cancellationToken)
{
var backups = await useCase.ExecuteAsync(cancellationToken);
- return TypedResults.Ok(new BackupListResponse(backups.Select(ToResponse).ToList()));
+ var responses = new List(backups.Count);
+ foreach (var backup in backups)
+ responses.Add(ToResponse(backup, await evidence.GetAsync(backup.BackupId, cancellationToken)));
+ return TypedResults.Ok(new BackupListResponse(responses));
}
private static async Task> CreateAsync(
- CreateBackupUseCase useCase, ClaimsPrincipal principal, HttpContext httpContext, CancellationToken cancellationToken)
+ CreateBackupUseCase useCase, IBackupEvidenceStore evidence, ClaimsPrincipal principal, HttpContext httpContext, CancellationToken cancellationToken)
{
var correlationId = httpContext.Items[CorrelationIdMiddleware.ItemKey] as string;
var actorId = Guid.Parse(principal.FindFirstValue(ClaimTypes.NameIdentifier)!);
var summary = await useCase.ExecuteAsync(new CreateBackupCommand(actorId, correlationId), cancellationToken);
- return TypedResults.Ok(ToResponse(summary));
+ return TypedResults.Ok(ToResponse(summary, await evidence.GetAsync(summary.BackupId, cancellationToken)));
}
- private static BackupSummaryResponse ToResponse(BackupSummary summary) =>
+ private static BackupSummaryResponse ToResponse(BackupSummary summary, BackupEvidence? evidence) =>
new(
summary.BackupId, summary.CreatedAt, summary.TotalSizeBytes, summary.TableCount,
- summary.TotalRowCount, summary.AttachmentCount, summary.SchemaVersion, summary.ProductVersion);
+ summary.TotalRowCount, summary.AttachmentCount, summary.SchemaVersion, summary.ProductVersion,
+ evidence?.Protected ?? false, (evidence?.ProtectionVersion ?? 0).ToString(CultureInfo.InvariantCulture),
+ evidence?.IntegrityVerifiedAt, evidence?.RestoreVerifiedAt, evidence?.LastRestoreSucceeded);
+
+ private static async Task> StatusAsync(
+ ListBackupsUseCase backups, IBackupEvidenceStore evidence, IPlatformSettingsReader settings,
+ IConfiguration configuration, TimeProvider clock, CancellationToken cancellationToken)
+ {
+ var policy = (await settings.GetEffectiveAsync(cancellationToken)).Values.BackupPolicyExpectation;
+ var items = await backups.ExecuteAsync(cancellationToken);
+ var receipts = new List();
+ BackupEvidence? latestReceipt = null;
+ foreach (var backup in items)
+ if (await evidence.GetAsync(backup.BackupId, cancellationToken) is { } receipt)
+ {
+ receipts.Add(receipt);
+ if (items.Count > 0 && backup.BackupId == items[0].BackupId) latestReceipt = receipt;
+ }
+ DateTimeOffset? last = items.Count > 0 ? items[0].CreatedAt : null;
+ var integrity = receipts.Select(r => r.IntegrityVerifiedAt).Max();
+ var restore = receipts.Select(r => r.RestoreVerifiedAt).Max();
+ var now = clock.GetUtcNow();
+ var state = last is null || latestReceipt?.IntegrityVerifiedAt is null ? "Unknown"
+ : now - last > TimeSpan.FromHours(policy.ExpectedIntervalHours) ? "Stale" : "Current";
+ var restoreState = restore is null ? "Unknown"
+ : now - restore > TimeSpan.FromDays(policy.RestoreVerificationMaxAgeDays) ? "Stale" : "Verified";
+ return TypedResults.Ok(new BackupStatusResponse(state, "External", policy.ScheduleDescription, null,
+ last?.AddHours(policy.ExpectedIntervalHours), last, integrity, restore, restoreState,
+ items.Sum(b => b.TotalSizeBytes), receipts.Count(r => r.Protected), policy.ExpectedIntervalHours,
+ configuration.GetValue("KnowledgeHub:Backup:RetainedCount") ?? 14,
+ policy.DailyRetentionCount, policy.WeeklyRetentionCount, policy.MonthlyRetentionCount,
+ policy.RestoreVerificationMaxAgeDays));
+ }
+
+ private static async Task ProtectAsync(
+ string backupId, BackupProtectionRequest request, IBackupEvidenceStore evidence,
+ IAuditEventRecorder audit, HttpContext context, TimeProvider clock, CancellationToken cancellationToken)
+ {
+ var header = context.Request.Headers.IfMatch.ToString();
+ if (header.Length < 3 || header[0] != '"' || header[^1] != '"'
+ || !int.TryParse(header[1..^1], NumberStyles.None, CultureInfo.InvariantCulture, out var version))
+ return Results.Problem(statusCode: 428, title: "If-Match is verplicht.");
+ var actor = Guid.Parse(context.User.FindFirstValue(ClaimTypes.NameIdentifier)!);
+ BackupProtectionResult result;
+ try { result = await evidence.SetProtectionAsync(backupId, request.Protected, version, actor, cancellationToken); }
+ catch (IOException) { return Results.Problem(statusCode: 409, title: "Back-upopslag is in gebruik; probeer opnieuw."); }
+ if (result == BackupProtectionResult.NotFound) return Results.NotFound();
+ if (result == BackupProtectionResult.Conflict) return Results.Problem(statusCode: 412, title: "De bescherming is intussen gewijzigd.");
+ await audit.RecordAsync(new AuditEventRecord(AuditActorKind.User, actor, "backup",
+ request.Protected ? "BackupProtected" : "BackupUnprotected", "Backup", backupId,
+ request.Protected ? "Back-up beschermd tegen automatische retentie." : "Back-upbescherming verwijderd.",
+ context.Items[CorrelationIdMiddleware.ItemKey] as string, clock.GetUtcNow()), cancellationToken);
+ var receipt = await evidence.GetAsync(backupId, cancellationToken);
+ context.Response.Headers.ETag = $"\"{receipt!.ProtectionVersion}\"";
+ return Results.Ok(receipt);
+ }
}
diff --git a/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/DataSourcesEndpoints.cs b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/DataSourcesEndpoints.cs
new file mode 100644
index 0000000..8eddbb3
--- /dev/null
+++ b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/DataSourcesEndpoints.cs
@@ -0,0 +1,73 @@
+using System.Globalization;
+using System.Security.Claims;
+using ItWorx.KnowledgeHub.Api.Authorization;
+using ItWorx.KnowledgeHub.Api.Security;
+using ItWorx.KnowledgeHub.Application.Common;
+using ItWorx.KnowledgeHub.Application.Identity;
+using ItWorx.KnowledgeHub.Application.Sources;
+using ItWorx.KnowledgeHub.Contracts.Sources;
+using ItWorx.KnowledgeHub.Domain.Sources;
+using Microsoft.AspNetCore.Mvc;
+
+namespace ItWorx.KnowledgeHub.Api.Endpoints;
+
+public static class DataSourcesEndpoints
+{
+ public static IEndpointRouteBuilder MapDataSourcesEndpoints(this IEndpointRouteBuilder app)
+ {
+ var group = app.MapGroup("/api/v1/admin/data-sources").WithTags("DataSources").RequireAuthorization(AuthorizationPolicies.RequireAdministrator);
+ group.MapGet("", async (DataSourceUseCases useCases, IWorkspaceRepository workspaces, TimeProvider clock, [FromQuery] Guid? cursor, [FromQuery] int? limit, CancellationToken cancellationToken) =>
+ {
+ var workspace = await workspaces.GetDefaultWorkspaceIdAsync(cancellationToken);
+ if (workspace is null) return Results.NotFound();
+ var pageSize = Math.Clamp(limit ?? 50, 1, 100);
+ var rows = await useCases.ListAsync(workspace.Value, cursor, pageSize + 1, cancellationToken);
+ var items = rows.Take(pageSize).Select(source => ToResponse(source, clock.GetUtcNow())).ToArray();
+ return Results.Ok(new DataSourceListResponse(items, rows.Count > pageSize ? items[^1].Id.ToString() : null));
+ });
+ group.MapPost("", async (RegisterDataSourceRequest request, DataSourceUseCases useCases, IWorkspaceRepository workspaces, HttpContext context, TimeProvider clock, CancellationToken cancellationToken) =>
+ {
+ var workspace = await workspaces.GetDefaultWorkspaceIdAsync(cancellationToken);
+ if (workspace is null) return Results.NotFound();
+ var result = await useCases.RegisterAsync(workspace.Value, request.SourceKey, request.DisplayName, request.Kind, request.ExpectedIntervalHours, Actor(context), Correlation(context), cancellationToken);
+ return Result(result, context, clock);
+ }).AddEndpointFilter();
+ group.MapPut("/{id:guid}", async (Guid id, ConfigureDataSourceRequest request, DataSourceUseCases useCases, IWorkspaceRepository workspaces, HttpContext context, TimeProvider clock, CancellationToken cancellationToken) =>
+ {
+ if (!Expected(context, out var expected)) return Results.Problem(statusCode: 428, title: "If-Match is verplicht.");
+ var workspace = await workspaces.GetDefaultWorkspaceIdAsync(cancellationToken);
+ if (workspace is null) return Results.NotFound();
+ return Result(await useCases.ConfigureAsync(workspace.Value, id, expected, request.DisplayName, request.Enabled, request.ExpectedIntervalHours, Actor(context), Correlation(context), cancellationToken), context, clock);
+ }).AddEndpointFilter();
+ group.MapPost("/{id:guid}/observations", async (Guid id, ReportDataSourceRequest request, DataSourceUseCases useCases, IWorkspaceRepository workspaces, HttpContext context, TimeProvider clock, CancellationToken cancellationToken) =>
+ {
+ if (!Expected(context, out var expected)) return Results.Problem(statusCode: 428, title: "If-Match is verplicht.");
+ var workspace = await workspaces.GetDefaultWorkspaceIdAsync(cancellationToken);
+ if (workspace is null) return Results.NotFound();
+ return Result(await useCases.ReportAsync(workspace.Value, id, expected, request.Success, request.RecordCount, request.ErrorCode, request.CorrelationId, Actor(context), Correlation(context), cancellationToken), context, clock);
+ }).AddEndpointFilter();
+ return app;
+ }
+
+ private static bool Expected(HttpContext context, out string value)
+ {
+ var header = context.Request.Headers.IfMatch.ToString();
+ value = header.Length > 2 && header.StartsWith('"') && header.EndsWith('"') ? header[1..^1] : "";
+ return int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out var version) && version > 0;
+ }
+ private static Guid Actor(HttpContext context) => Guid.Parse(context.User.FindFirstValue(ClaimTypes.NameIdentifier)!);
+ private static string? Correlation(HttpContext context) => context.Items[CorrelationIdMiddleware.ItemKey] as string;
+ private static IResult Result(OperationResultValue result, HttpContext context, TimeProvider clock)
+ {
+ if (result.Status == OperationStatus.NotFound) return Results.NotFound();
+ if (result.Status == OperationStatus.ConcurrencyConflict) return Results.Problem(statusCode: 412, title: "De bron is gewijzigd; laad de actuele versie.");
+ if (result.Status != OperationStatus.Success) return Results.Problem(statusCode: 400, title: "Ongeldige bronconfiguratie.");
+ var response = ToResponse(result.Value!, clock.GetUtcNow());
+ context.Response.Headers.ETag = $"\"{response.ETag}\"";
+ return Results.Ok(response);
+ }
+ private static DataSourceResponse ToResponse(DataSource source, DateTimeOffset now) => new(source.Id, source.SourceKey, source.DisplayName, source.Kind.ToString(), source.Enabled,
+ source.GetState(now), source.LastAttemptAt is null ? "Unknown" : source.LastErrorCode == "authentication" ? "NeedsAuthorization" : "Reported",
+ "External", source.ExpectedIntervalHours, source.LastAttemptAt, source.LastSuccessAt, source.LastFailureAt, source.DataObservedAt, source.RecordCount,
+ source.LastErrorCode, source.LastCorrelationId, source.Version.ToString(CultureInfo.InvariantCulture), ["configure", "record-observation"]);
+}
diff --git a/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/IngestionProposalsEndpoints.cs b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/IngestionProposalsEndpoints.cs
index 23ea029..eed6c7d 100644
--- a/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/IngestionProposalsEndpoints.cs
+++ b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/IngestionProposalsEndpoints.cs
@@ -7,11 +7,11 @@ using ItWorx.KnowledgeHub.Application.Common;
using ItWorx.KnowledgeHub.Application.Identity;
using ItWorx.KnowledgeHub.Application.Ingestion;
using ItWorx.KnowledgeHub.Application.Ingestion.UseCases;
+using ItWorx.KnowledgeHub.Application.Platform;
using ItWorx.KnowledgeHub.Contracts.Ingestion;
using ItWorx.KnowledgeHub.Domain.Identity;
using ItWorx.KnowledgeHub.Domain.Ingestion;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Options;
namespace ItWorx.KnowledgeHub.Api.Endpoints;
@@ -48,10 +48,11 @@ public static class IngestionProposalsEndpoints
}
private static async Task CreateAsync([FromBody] CreateIngestionProposalRequest request, IWorkspaceRepository workspaces,
- CreateIngestionProposalUseCase useCase, IIngestionProposalRepository proposals, IngestionMetrics metrics, IOptions options,
+ CreateIngestionProposalUseCase useCase, IIngestionProposalRepository proposals, IngestionMetrics metrics, IPlatformSettingsReader platformSettings,
ClaimsPrincipal principal, HttpContext context, CancellationToken ct)
{
- if (!options.Value.Enabled) return Disabled();
+ var policy = (await platformSettings.GetEffectiveAsync(ct)).Values.IngestionProposals;
+ if (!policy.Enabled) return Disabled();
if (string.IsNullOrWhiteSpace(context.Request.Headers["Idempotency-Key"].ToString()))
return Results.Problem(statusCode: 400, detail: "Idempotency-Key-header is vereist.");
var workspace = await workspaces.GetDefaultWorkspaceIdAsync(ct);
@@ -73,7 +74,8 @@ public static class IngestionProposalsEndpoints
}
}
var result = await useCase.ExecuteAsync(new(workspace.Value, Actor(principal), request.Manifest,
- request.CorrelationId ?? Correlation(context), context.Request.Headers["Idempotency-Key"].ToString(), publication), ct);
+ request.CorrelationId ?? Correlation(context), context.Request.Headers["Idempotency-Key"].ToString(), publication,
+ policy.FailedRetryLimit), ct);
metrics.RecordProposal(result.Status == OperationStatus.Success ? "success" : "failure",
result.Status == OperationStatus.Success ? "none" : "validation");
if (result.Status != OperationStatus.Success) return Results.Problem(statusCode: 422, detail: string.Join(' ', result.Errors));
@@ -84,11 +86,12 @@ public static class IngestionProposalsEndpoints
private static async Task ListAsync(IWorkspaceRepository workspaces, IWorkspaceMembershipRepository memberships,
IIngestionProposalRepository proposals,
- IOptions options, HttpContext context, [FromQuery] string? status, [FromQuery] Guid? cursor,
+ IPlatformSettingsReader platformSettings, HttpContext context, [FromQuery] string? status, [FromQuery] Guid? cursor,
[FromQuery] int? limit, [FromQuery] int? page, [FromQuery] int? pageSize, [FromQuery] Guid? projectId,
[FromQuery] Guid? submitterId, [FromQuery] int? minAgeDays, [FromQuery] string? risk, [FromQuery] string? documentType, CancellationToken ct)
{
- if (!options.Value.Enabled) return Disabled();
+ var policy = (await platformSettings.GetEffectiveAsync(ct)).Values.IngestionProposals;
+ if (!policy.Enabled) return Disabled();
var workspace = await workspaces.GetDefaultWorkspaceIdAsync(ct);
if (workspace is null) return Results.Ok(new IngestionProposalListResponse([], null, 1, 50, 0, Correlation(context)));
if (!TryStatus(status, out var parsed)) return Results.Problem(statusCode: 400, detail: "Ongeldige statusfilter.");
@@ -116,9 +119,10 @@ public static class IngestionProposalsEndpoints
private static async Task GetAsync(Guid proposalId, IWorkspaceRepository workspaces, IWorkspaceMembershipRepository memberships,
IIngestionProposalRepository proposals,
- IOptions options, ClaimsPrincipal principal, HttpContext context, CancellationToken ct)
+ IPlatformSettingsReader platformSettings, ClaimsPrincipal principal, HttpContext context, CancellationToken ct)
{
- if (!options.Value.Enabled) return Disabled();
+ var policy = (await platformSettings.GetEffectiveAsync(ct)).Values.IngestionProposals;
+ if (!policy.Enabled) return Disabled();
var workspace = await workspaces.GetDefaultWorkspaceIdAsync(ct);
if (workspace is null) return NotFound();
var proposal = await proposals.FindReadOnlyAsync(workspace.Value, proposalId, ct);
@@ -132,10 +136,11 @@ public static class IngestionProposalsEndpoints
private static async Task PatchAsync(Guid proposalId, [FromBody] PatchIngestionProposalRequest request, IWorkspaceRepository workspaces,
PatchIngestionProposalUseCase useCase, ApplyIngestionProposalAnalysisUseCase analysisUseCase,
- IWorkspaceMembershipRepository memberships, IIngestionProposalRepository proposals, IOptions options,
+ IWorkspaceMembershipRepository memberships, IIngestionProposalRepository proposals, IPlatformSettingsReader platformSettings,
ClaimsPrincipal principal, HttpContext context, CancellationToken ct)
{
- if (!options.Value.Enabled) return Disabled();
+ var policy = (await platformSettings.GetEffectiveAsync(ct)).Values.IngestionProposals;
+ if (!policy.Enabled) return Disabled();
var workspace = await workspaces.GetDefaultWorkspaceIdAsync(ct); if (workspace is null) return NotFound();
var proposal = await proposals.FindReadOnlyAsync(workspace.Value, proposalId, ct);
if (proposal is null || (IsServiceAccount(principal) && proposal.IntegrationActorId != Actor(principal))) return NotFound();
@@ -160,19 +165,20 @@ public static class IngestionProposalsEndpoints
}
private static Task RequestChangesAsync(Guid proposalId, [FromBody] IngestionProposalDecisionRequest request, IWorkspaceRepository workspaces,
- DecideIngestionProposalUseCase useCase, IIngestionProposalRepository proposals, IOptions options, ClaimsPrincipal principal, HttpContext context, CancellationToken ct) =>
- DecideAsync(IngestionDecision.RequestChanges, proposalId, request, workspaces, useCase, proposals, options, principal, context, ct);
+ DecideIngestionProposalUseCase useCase, IIngestionProposalRepository proposals, IPlatformSettingsReader platformSettings, ClaimsPrincipal principal, HttpContext context, CancellationToken ct) =>
+ DecideAsync(IngestionDecision.RequestChanges, proposalId, request, workspaces, useCase, proposals, platformSettings, principal, context, ct);
private static Task ApproveAsync(Guid proposalId, [FromBody] IngestionProposalDecisionRequest request, IWorkspaceRepository workspaces,
- DecideIngestionProposalUseCase useCase, IIngestionProposalRepository proposals, IOptions options, ClaimsPrincipal principal, HttpContext context, CancellationToken ct) =>
- DecideAsync(IngestionDecision.Approve, proposalId, request, workspaces, useCase, proposals, options, principal, context, ct);
+ DecideIngestionProposalUseCase useCase, IIngestionProposalRepository proposals, IPlatformSettingsReader platformSettings, ClaimsPrincipal principal, HttpContext context, CancellationToken ct) =>
+ DecideAsync(IngestionDecision.Approve, proposalId, request, workspaces, useCase, proposals, platformSettings, principal, context, ct);
private static Task RejectAsync(Guid proposalId, [FromBody] IngestionProposalDecisionRequest request, IWorkspaceRepository workspaces,
- DecideIngestionProposalUseCase useCase, IIngestionProposalRepository proposals, IOptions options, ClaimsPrincipal principal, HttpContext context, CancellationToken ct) =>
- DecideAsync(IngestionDecision.Reject, proposalId, request, workspaces, useCase, proposals, options, principal, context, ct);
+ DecideIngestionProposalUseCase useCase, IIngestionProposalRepository proposals, IPlatformSettingsReader platformSettings, ClaimsPrincipal principal, HttpContext context, CancellationToken ct) =>
+ DecideAsync(IngestionDecision.Reject, proposalId, request, workspaces, useCase, proposals, platformSettings, principal, context, ct);
private static async Task DecideAsync(IngestionDecision decision, Guid proposalId, IngestionProposalDecisionRequest request, IWorkspaceRepository workspaces,
- DecideIngestionProposalUseCase useCase, IIngestionProposalRepository proposals, IOptions options, ClaimsPrincipal principal, HttpContext context, CancellationToken ct)
+ DecideIngestionProposalUseCase useCase, IIngestionProposalRepository proposals, IPlatformSettingsReader platformSettings, ClaimsPrincipal principal, HttpContext context, CancellationToken ct)
{
- if (!options.Value.Enabled) return Disabled(); var workspace = await workspaces.GetDefaultWorkspaceIdAsync(ct); if (workspace is null) return NotFound();
+ var policy = (await platformSettings.GetEffectiveAsync(ct)).Values.IngestionProposals;
+ if (!policy.Enabled) return Disabled(); var workspace = await workspaces.GetDefaultWorkspaceIdAsync(ct); if (workspace is null) return NotFound();
if (ETagHelper.FromHeaderValue(context.Request.Headers.IfMatch) is null)
return Results.Problem(statusCode: 428, detail: "If-Match-header is vereist.");
var started = System.Diagnostics.Stopwatch.GetTimestamp();
@@ -183,9 +189,10 @@ public static class IngestionProposalsEndpoints
}
private static async Task RetryAsync(Guid proposalId, IWorkspaceRepository workspaces, RetryIngestionProposalUseCase useCase,
- IIngestionProposalRepository proposals, IOptions options, ClaimsPrincipal principal, HttpContext context, CancellationToken ct)
+ IIngestionProposalRepository proposals, IPlatformSettingsReader platformSettings, ClaimsPrincipal principal, HttpContext context, CancellationToken ct)
{
- if (!options.Value.Enabled) return Disabled(); var workspace = await workspaces.GetDefaultWorkspaceIdAsync(ct); if (workspace is null) return NotFound();
+ var policy = (await platformSettings.GetEffectiveAsync(ct)).Values.IngestionProposals;
+ if (!policy.Enabled) return Disabled(); var workspace = await workspaces.GetDefaultWorkspaceIdAsync(ct); if (workspace is null) return NotFound();
if (ETagHelper.FromHeaderValue(context.Request.Headers.IfMatch) is null)
return Results.Problem(statusCode: 428, detail: "If-Match-header is vereist.");
var result = await useCase.ExecuteAsync(new(workspace.Value, proposalId, Actor(principal), ETag(context)), ct);
@@ -194,9 +201,10 @@ public static class IngestionProposalsEndpoints
private static async Task PublishAsync(Guid proposalId, IWorkspaceRepository workspaces,
PublishIngestionProposalUseCase useCase, IIngestionProposalRepository proposals, IngestionMetrics metrics,
- IOptions options, ClaimsPrincipal principal, HttpContext context, CancellationToken ct)
+ IPlatformSettingsReader platformSettings, ClaimsPrincipal principal, HttpContext context, CancellationToken ct)
{
- if (!options.Value.Enabled) return Disabled();
+ var policy = (await platformSettings.GetEffectiveAsync(ct)).Values.IngestionProposals;
+ if (!policy.Enabled) return Disabled();
if (IsServiceAccount(principal)) return Results.Problem(statusCode: 403, detail: "Serviceaccounts mogen geen voorstellen publiceren.");
var workspace = await workspaces.GetDefaultWorkspaceIdAsync(ct); if (workspace is null) return NotFound();
if (ETagHelper.FromHeaderValue(context.Request.Headers.IfMatch) is null)
@@ -230,16 +238,17 @@ public static class IngestionProposalsEndpoints
private static async Task UploadSourceAsync(Guid proposalId, IFormFile file, IWorkspaceRepository workspaces,
StageIngestionProposalSourceUseCase useCase, IIngestionProposalRepository proposals, IngestionMetrics metrics,
- IOptions options, ClaimsPrincipal principal, HttpContext context, CancellationToken ct)
+ IPlatformSettingsReader platformSettings, ClaimsPrincipal principal, HttpContext context, CancellationToken ct)
{
- if (!options.Value.Enabled) return Disabled(); var workspace = await workspaces.GetDefaultWorkspaceIdAsync(ct); if (workspace is null) return NotFound();
+ var policy = (await platformSettings.GetEffectiveAsync(ct)).Values.IngestionProposals;
+ if (!policy.Enabled) return Disabled(); var workspace = await workspaces.GetDefaultWorkspaceIdAsync(ct); if (workspace is null) return NotFound();
if (ETagHelper.FromHeaderValue(context.Request.Headers.IfMatch) is null)
return Results.Problem(statusCode: 428, detail: "If-Match-header is vereist.");
- if (file.Length > options.Value.MaximumSourceBytes) return Results.Problem(statusCode: 413, detail: "Bron is groter dan de toegestane limiet.");
+ if (file.Length > policy.MaximumSourceBytes) return Results.Problem(statusCode: 413, detail: "Bron is groter dan de toegestane limiet.");
await using var source = file.OpenReadStream();
var started = System.Diagnostics.Stopwatch.GetTimestamp();
var result = await useCase.ExecuteAsync(new(workspace.Value, proposalId, Actor(principal), file.FileName, file.ContentType,
- source, Correlation(context), options.Value.MaximumSourceCount, ETag(context), ActorKind(principal), AuditSource(principal)), ct);
+ source, Correlation(context), policy.MaximumSourceCount, ETag(context), ActorKind(principal), AuditSource(principal)), ct);
metrics.RecordStageDuration("source_staging", result.Status == OperationStatus.Success ? "success" : "failure",
System.Diagnostics.Stopwatch.GetElapsedTime(started).TotalSeconds);
if (result.Status != OperationStatus.Success) return await ToMutationResult(result, proposals, workspace.Value, proposalId, context, ct);
@@ -251,9 +260,10 @@ public static class IngestionProposalsEndpoints
private static async Task SubmitManualAsync(Guid proposalId, IWorkspaceRepository workspaces,
SubmitManualIngestionProposalUseCase useCase, IIngestionProposalRepository proposals,
- IOptions options, ClaimsPrincipal principal, HttpContext context, CancellationToken ct)
+ IPlatformSettingsReader platformSettings, ClaimsPrincipal principal, HttpContext context, CancellationToken ct)
{
- if (!options.Value.Enabled) return Disabled();
+ var policy = (await platformSettings.GetEffectiveAsync(ct)).Values.IngestionProposals;
+ if (!policy.Enabled) return Disabled();
if (IsServiceAccount(principal)) return Results.Forbid();
var workspace = await workspaces.GetDefaultWorkspaceIdAsync(ct);
if (workspace is null) return NotFound();
diff --git a/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/JobsEndpoints.cs b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/JobsEndpoints.cs
new file mode 100644
index 0000000..352edee
--- /dev/null
+++ b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/JobsEndpoints.cs
@@ -0,0 +1,277 @@
+using System.Security.Claims;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using ItWorx.KnowledgeHub.Api.Authorization;
+using ItWorx.KnowledgeHub.Api.Security;
+using ItWorx.KnowledgeHub.Application.Identity;
+using ItWorx.KnowledgeHub.Application.Jobs;
+using ItWorx.KnowledgeHub.Application.Jobs.UseCases;
+using ItWorx.KnowledgeHub.Contracts.Errors;
+using ItWorx.KnowledgeHub.Contracts.Jobs;
+using ItWorx.KnowledgeHub.Domain.Identity;
+using ItWorx.KnowledgeHub.Domain.Jobs;
+using Microsoft.AspNetCore.Http.HttpResults;
+using Microsoft.AspNetCore.Mvc;
+
+namespace ItWorx.KnowledgeHub.Api.Endpoints;
+
+/// Cookie-admin-only control surface for durable jobs. Mutations require CSRF;
+/// event streams expose only the latest persisted safe job snapshot.
+public static class JobsEndpoints
+{
+ private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
+ {
+ Converters = { new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower) }
+ };
+
+ public static IEndpointRouteBuilder MapJobsEndpoints(this IEndpointRouteBuilder app)
+ {
+ var group = app.MapGroup("/api/v1/jobs")
+ .WithTags("Jobs")
+ .RequireAuthorization(AuthorizationPolicies.RequireAdministrator);
+
+ group.MapPost("/course-tagging", QueueCourseTaggingAsync)
+ .RequireRateLimiting(RateLimitPolicies.JobCreation)
+ .AddEndpointFilter();
+ group.MapPost("/backups", QueueBackupAsync)
+ .RequireRateLimiting(RateLimitPolicies.JobCreation)
+ .AddEndpointFilter();
+ group.MapGet("", ListAsync);
+ group.MapGet("/{jobId:guid}", GetAsync);
+ group.MapPost("/{jobId:guid}/cancel", CancelAsync)
+ .AddEndpointFilter();
+ group.MapGet("/{jobId:guid}/events", EventsAsync);
+ return app;
+ }
+
+ private static async Task QueueCourseTaggingAsync(
+ [FromBody] QueueCourseTaggingJobRequest request,
+ QueueBackgroundJobUseCase useCase,
+ IWorkspaceRepository workspaces,
+ HttpContext context,
+ CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrWhiteSpace(context.Request.Headers["Idempotency-Key"]))
+ return Results.Problem(statusCode: 400, type: ProblemTypes.Validation, title: "Idempotency-Key is verplicht.");
+ var workspaceId = await workspaces.GetDefaultWorkspaceIdAsync(cancellationToken);
+ if (workspaceId is null)
+ return Results.Problem(statusCode: 404, type: ProblemTypes.NotFound);
+
+ EnqueueJobResult result;
+ try
+ {
+ result = await useCase.QueueCourseTaggingAsync(
+ new QueueCourseTaggingJobCommand(
+ workspaceId.Value, CurrentUserId(context.User), request.RootPageId, request.Tag,
+ context.Request.Headers["Idempotency-Key"].ToString(), CorrelationId(context)),
+ cancellationToken);
+ }
+ catch (ArgumentException)
+ {
+ return Results.Problem(statusCode: 400, type: ProblemTypes.Validation, title: "De cursustagtaak is ongeldig.");
+ }
+ return ToEnqueueResult(result);
+ }
+
+ private static async Task QueueBackupAsync(
+ QueueBackupJobRequest _,
+ QueueBackgroundJobUseCase useCase,
+ IWorkspaceRepository workspaces,
+ HttpContext context,
+ CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrWhiteSpace(context.Request.Headers["Idempotency-Key"]))
+ return Results.Problem(statusCode: 400, type: ProblemTypes.Validation, title: "Idempotency-Key is verplicht.");
+ var workspaceId = await workspaces.GetDefaultWorkspaceIdAsync(cancellationToken);
+ if (workspaceId is null)
+ return Results.Problem(statusCode: 404, type: ProblemTypes.NotFound);
+ EnqueueJobResult result;
+ try
+ {
+ result = await useCase.QueueBackupAsync(
+ new QueueJobCommand(workspaceId.Value, CurrentUserId(context.User),
+ context.Request.Headers["Idempotency-Key"].ToString(), CorrelationId(context)), cancellationToken);
+ }
+ catch (ArgumentException)
+ {
+ return Results.Problem(statusCode: 400, type: ProblemTypes.Validation, title: "Een geldige Idempotency-Key is verplicht.");
+ }
+ return ToEnqueueResult(result);
+ }
+
+ private static async Task, ProblemHttpResult>> ListAsync(
+ BackgroundJobQueries queries,
+ IWorkspaceRepository workspaces,
+ [FromQuery] int? limit,
+ CancellationToken cancellationToken)
+ {
+ var workspaceId = await workspaces.GetDefaultWorkspaceIdAsync(cancellationToken);
+ if (workspaceId is null)
+ return TypedResults.Problem(statusCode: 404, type: ProblemTypes.NotFound);
+ var items = await queries.ListAsync(workspaceId.Value, limit ?? 50, cancellationToken);
+ return TypedResults.Ok(new BackgroundJobListResponse(items.Select(ToResponse).ToList()));
+ }
+
+ private static async Task, NotFound>> GetAsync(
+ Guid jobId,
+ BackgroundJobQueries queries,
+ IWorkspaceRepository workspaces,
+ CancellationToken cancellationToken)
+ {
+ var workspaceId = await workspaces.GetDefaultWorkspaceIdAsync(cancellationToken);
+ if (workspaceId is null)
+ return TypedResults.NotFound();
+ var job = await queries.GetAsync(workspaceId.Value, jobId, cancellationToken);
+ return job is null ? TypedResults.NotFound() : TypedResults.Ok(ToResponse(job));
+ }
+
+ private static async Task CancelAsync(
+ Guid jobId,
+ CancelBackgroundJobUseCase useCase,
+ IWorkspaceRepository workspaces,
+ HttpContext context,
+ CancellationToken cancellationToken)
+ {
+ var workspaceId = await workspaces.GetDefaultWorkspaceIdAsync(cancellationToken);
+ if (workspaceId is null)
+ return Results.NotFound();
+ var (status, job) = await useCase.ExecuteAsync(
+ workspaceId.Value, jobId, CurrentUserId(context.User), CorrelationId(context), cancellationToken);
+ if (status == CancelBackgroundJobStatus.NotFound || job is null)
+ return Results.NotFound();
+ return Results.Ok(ToResponse(job));
+ }
+
+ private static async Task EventsAsync(
+ Guid jobId,
+ BackgroundJobQueries queries,
+ IWorkspaceRepository workspaces,
+ IUserAccountManager users,
+ IWorkspaceMembershipRepository memberships,
+ BackgroundJobsOptions options,
+ TimeProvider timeProvider,
+ HttpContext context,
+ CancellationToken cancellationToken)
+ {
+ var workspaceId = await workspaces.GetDefaultWorkspaceIdAsync(cancellationToken);
+ if (workspaceId is null)
+ return Results.NotFound();
+ var initial = await queries.GetAsync(workspaceId.Value, jobId, cancellationToken);
+ if (initial is null)
+ return Results.NotFound();
+
+ context.Response.StatusCode = StatusCodes.Status200OK;
+ context.Response.ContentType = "text/event-stream; charset=utf-8";
+ context.Response.Headers["Cache-Control"] = "no-cache, no-transform";
+ context.Response.Headers.Append("X-Accel-Buffering", "no");
+ var actorId = CurrentUserId(context.User);
+ var lastVersion = -1L;
+ var lastHeartbeat = timeProvider.GetUtcNow();
+
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ var actor = await users.FindByIdAsync(actorId, cancellationToken);
+ var role = actor is { Enabled: true }
+ ? await memberships.GetRoleAsync(workspaceId.Value, actorId, cancellationToken)
+ : null;
+ if (role != WorkspaceRole.Administrator)
+ break;
+
+ var job = await queries.GetAsync(workspaceId.Value, jobId, cancellationToken);
+ if (job is null)
+ break;
+ if (job.IsTerminal)
+ {
+ // Terminal state must close every stream, even if a concurrent
+ // worker update left the terminal snapshot at the last event ID.
+ await WriteEventAsync(context, "terminal", job, cancellationToken);
+ break;
+ }
+ if (job.Version != lastVersion)
+ {
+ var eventName = lastVersion < 0 ? "job" : "progress";
+ await WriteEventAsync(context, eventName, job, cancellationToken);
+ lastVersion = job.Version;
+ lastHeartbeat = timeProvider.GetUtcNow();
+ }
+ else if (timeProvider.GetUtcNow() - lastHeartbeat >= TimeSpan.FromSeconds(options.SseHeartbeatSeconds))
+ {
+ await context.Response.WriteAsync(": heartbeat\n\n", cancellationToken);
+ await context.Response.Body.FlushAsync(cancellationToken);
+ lastHeartbeat = timeProvider.GetUtcNow();
+ }
+
+ await Task.Delay(TimeSpan.FromMilliseconds(options.SsePollMilliseconds), timeProvider, cancellationToken);
+ }
+ return Results.Empty;
+ }
+
+ private static async Task WriteEventAsync(HttpContext context, string eventName, BackgroundJob job, CancellationToken cancellationToken)
+ {
+ var data = JsonSerializer.Serialize(ToResponse(job), JsonOptions);
+ await context.Response.WriteAsync($"id: {job.Version}\nevent: {eventName}\ndata: {data}\n\n", cancellationToken);
+ await context.Response.Body.FlushAsync(cancellationToken);
+ }
+
+ private static IResult ToEnqueueResult(EnqueueJobResult result) => result.Status switch
+ {
+ EnqueueJobStatus.Created or EnqueueJobStatus.Existing when result.Job is not null =>
+ Results.Accepted($"/api/v1/jobs/{result.Job.Id:D}", ToResponse(result.Job)),
+ EnqueueJobStatus.IdempotencyConflict => Results.Problem(
+ statusCode: 409, type: ProblemTypes.IdempotencyKeyConflict,
+ title: "Idempotency-Key is al gebruikt voor een andere aanvraag."),
+ EnqueueJobStatus.RootNotFound => Results.Problem(statusCode: 404, type: ProblemTypes.PageNotFound, title: "Hoofdpagina niet gevonden."),
+ EnqueueJobStatus.TooManyPages => Results.Problem(
+ statusCode: 413, type: ProblemTypes.Validation,
+ title: "De cursuspaginaboom overschrijdt de ingestelde taaklimiet."),
+ EnqueueJobStatus.QueueFull => Results.Problem(
+ statusCode: 429, title: "De taaklimiet is bereikt. Wacht tot lopende taken klaar zijn of het dagbudget is hersteld."),
+ _ => Results.Problem(statusCode: 500, title: "De taak kon niet worden ingepland.")
+ };
+
+ private static BackgroundJobResponse ToResponse(BackgroundJob job)
+ {
+ var errors = Array.Empty();
+ try
+ {
+ errors = (JsonSerializer.Deserialize(job.ErrorsJson) ?? [])
+ .Select(error => new BackgroundJobErrorResponse(error.Code, error.Message, error.ItemId))
+ .ToArray();
+ }
+ catch (JsonException)
+ {
+ // A damaged error summary must not turn the read API into a 500 or expose raw storage.
+ }
+ JsonElement? result = null;
+ if (!string.IsNullOrWhiteSpace(job.ResultJson))
+ {
+ try { result = JsonDocument.Parse(job.ResultJson).RootElement.Clone(); }
+ catch (JsonException) { result = null; }
+ }
+
+ return new BackgroundJobResponse(
+ job.Id, job.WorkspaceId, job.ActorId,
+ job.Type switch { BackgroundJobType.CourseTagging => "course-tagging", BackgroundJobType.Backup => "backup", _ => "unknown" },
+ ToStatus(job.Status), job.CreatedAt, job.StartedAt, job.CompletedAt,
+ job.CancelRequestedAt, job.CancelRequestedAt is not null,
+ job.Progress, job.Total, job.Processed, job.Succeeded, job.Skipped, job.Failed,
+ errors, result, job.CorrelationId, job.Version);
+ }
+
+ private static string ToStatus(BackgroundJobStatus status) => status switch
+ {
+ BackgroundJobStatus.Queued => "queued",
+ BackgroundJobStatus.Running => "running",
+ BackgroundJobStatus.Succeeded => "succeeded",
+ BackgroundJobStatus.PartiallySucceeded => "partially_succeeded",
+ BackgroundJobStatus.Failed => "failed",
+ BackgroundJobStatus.Cancelled => "cancelled",
+ _ => "failed"
+ };
+
+ private static Guid CurrentUserId(ClaimsPrincipal principal) =>
+ Guid.Parse(principal.FindFirstValue(ClaimTypes.NameIdentifier)!);
+
+ private static string? CorrelationId(HttpContext context) =>
+ context.Items[CorrelationIdMiddleware.ItemKey] as string;
+}
diff --git a/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/PlatformEndpoints.cs b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/PlatformEndpoints.cs
new file mode 100644
index 0000000..164f8ea
--- /dev/null
+++ b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/PlatformEndpoints.cs
@@ -0,0 +1,276 @@
+using System.Globalization;
+using System.Security.Claims;
+using ItWorx.KnowledgeHub.Api.Authorization;
+using ItWorx.KnowledgeHub.Api.Knowledge;
+using ItWorx.KnowledgeHub.Api.Security;
+using ItWorx.KnowledgeHub.Application.Identity;
+using ItWorx.KnowledgeHub.Application.Platform;
+using ItWorx.KnowledgeHub.Application.Platform.UseCases;
+using ItWorx.KnowledgeHub.Contracts.Errors;
+using ItWorx.KnowledgeHub.Contracts.Platform;
+using ItWorx.KnowledgeHub.Domain.Identity;
+using ItWorx.KnowledgeHub.Domain.Platform;
+using Microsoft.AspNetCore.Http.HttpResults;
+using Microsoft.AspNetCore.Mvc;
+
+namespace ItWorx.KnowledgeHub.Api.Endpoints;
+
+public static class PlatformEndpoints
+{
+ public static IEndpointRouteBuilder MapPlatformEndpoints(this IEndpointRouteBuilder app)
+ {
+ var me = app.MapGroup("/api/v1/me")
+ .WithTags("Preferences")
+ .RequireAuthorization(AuthorizationPolicies.RequireReader);
+
+ me.MapGet("/preferences", GetUserPreferencesAsync);
+ me.MapPut("/preferences", UpdateUserPreferencesAsync)
+ .AddEndpointFilter();
+
+ var settings = app.MapGroup("/api/v1/admin/platform-settings")
+ .WithTags("Admin")
+ .RequireAuthorization(AuthorizationPolicies.RequireAdministrator);
+
+ settings.MapGet("", GetPlatformSettingsAsync);
+ settings.MapPut("", UpdatePlatformSettingsAsync)
+ .AddEndpointFilter();
+
+ return app;
+ }
+
+ private static async Task, ProblemHttpResult>> GetUserPreferencesAsync(
+ IWorkspaceRepository workspaces,
+ GetUserPreferencesUseCase useCase,
+ ClaimsPrincipal principal,
+ HttpContext context,
+ CancellationToken cancellationToken)
+ {
+ var workspaceId = await workspaces.GetDefaultWorkspaceIdAsync(cancellationToken);
+ if (workspaceId is null)
+ {
+ return TypedResults.Problem(statusCode: StatusCodes.Status404NotFound, type: ProblemTypes.NotFound);
+ }
+
+ var snapshot = await useCase.ExecuteAsync(workspaceId.Value, CurrentUserId(principal), cancellationToken);
+ var response = ToUserPreferencesResponse(snapshot);
+ context.Response.Headers.ETag = ETagHelper.ToHeaderValue(response.ETag);
+ return TypedResults.Ok(response);
+ }
+
+ private static async Task UpdateUserPreferencesAsync(
+ [FromBody] UserPreferencesRequest? request,
+ IWorkspaceRepository workspaces,
+ UpdateUserPreferencesUseCase useCase,
+ ClaimsPrincipal principal,
+ HttpContext context,
+ CancellationToken cancellationToken)
+ {
+ if (!TryGetExpectedVersion(context, out var expectedVersion, out var preconditionError))
+ {
+ return preconditionError!;
+ }
+
+ if (request is null)
+ {
+ return ValidationProblem("Voorkeuren ontbreken.");
+ }
+
+ var workspaceId = await workspaces.GetDefaultWorkspaceIdAsync(cancellationToken);
+ if (workspaceId is null)
+ {
+ return NotFound();
+ }
+
+ var values = new UserPreferencesValues(
+ request.Theme,
+ request.Density,
+ request.PrimaryNavigationCollapsed,
+ request.ContextPanelOpen,
+ request.NavigationOrder,
+ request.DashboardWidgetOrder,
+ request.HiddenDashboardWidgets,
+ request.DefaultLandingPage,
+ request.CatalogView,
+ request.ContextPanelWidth,
+ request.NavigationWidth);
+
+ var result = await useCase.ExecuteAsync(
+ new UpdateUserPreferencesCommand(workspaceId.Value, CurrentUserId(principal), expectedVersion, values),
+ cancellationToken);
+
+ if (result.Status == UpdateUserPreferencesStatus.PreconditionFailed)
+ {
+ return ConcurrencyProblem("De voorkeuren zijn sinds de laatste lezing gewijzigd.");
+ }
+ if (result.Status == UpdateUserPreferencesStatus.ValidationFailed)
+ {
+ return ValidationProblem(string.Join(" ", result.Errors));
+ }
+
+ var response = ToUserPreferencesResponse(result.Snapshot!);
+ context.Response.Headers.ETag = ETagHelper.ToHeaderValue(response.ETag);
+ return TypedResults.Ok(response);
+ }
+
+ private static async Task> GetPlatformSettingsAsync(
+ GetPlatformSettingsUseCase useCase,
+ HttpContext context,
+ CancellationToken cancellationToken)
+ {
+ var response = ToPlatformSettingsResponse(await useCase.ExecuteAsync(cancellationToken));
+ context.Response.Headers.ETag = ETagHelper.ToHeaderValue(response.ETag);
+ return TypedResults.Ok(response);
+ }
+
+ private static async Task UpdatePlatformSettingsAsync(
+ [FromBody] PlatformSettingsRequest? request,
+ UpdatePlatformSettingsUseCase useCase,
+ ClaimsPrincipal principal,
+ HttpContext context,
+ CancellationToken cancellationToken)
+ {
+ if (!TryGetExpectedVersion(context, out var expectedVersion, out var preconditionError))
+ {
+ return preconditionError!;
+ }
+
+ if (request is null || request.IngestionProposals is null
+ || request.BackupPolicyExpectation is null || request.InventoryFreshnessPolicy is null)
+ {
+ return ValidationProblem("Platforminstellingen ontbreken of zijn onvolledig.");
+ }
+
+ var values = new PlatformSettingsValues(
+ request.DisplayName,
+ request.PrimaryLocale,
+ request.TimeZone,
+ new IngestionProposalPolicyValues(
+ request.IngestionProposals.Enabled,
+ request.IngestionProposals.MaximumSourceCount,
+ request.IngestionProposals.MaximumSourceBytes,
+ request.IngestionProposals.FailedRetryLimit),
+ new BackupPolicyExpectationValues(
+ request.BackupPolicyExpectation.ExpectedIntervalHours,
+ request.BackupPolicyExpectation.ScheduleDescription,
+ request.BackupPolicyExpectation.DailyRetentionCount,
+ request.BackupPolicyExpectation.WeeklyRetentionCount,
+ request.BackupPolicyExpectation.MonthlyRetentionCount,
+ request.BackupPolicyExpectation.ProtectedBackupCount,
+ request.BackupPolicyExpectation.RestoreVerificationMaxAgeDays),
+ new InventoryFreshnessPolicyValues(request.InventoryFreshnessPolicy.WarningAfterHours));
+
+ var result = await useCase.ExecuteAsync(
+ new UpdatePlatformSettingsCommand(
+ expectedVersion,
+ CurrentUserId(principal),
+ context.Items[CorrelationIdMiddleware.ItemKey] as string,
+ values),
+ cancellationToken);
+
+ if (result.Status == UpdatePlatformSettingsStatus.PreconditionFailed)
+ {
+ return ConcurrencyProblem("De platforminstellingen zijn sinds de laatste lezing gewijzigd.");
+ }
+ if (result.Status == UpdatePlatformSettingsStatus.ValidationFailed)
+ {
+ return ValidationProblem(string.Join(" ", result.Errors));
+ }
+
+ var response = ToPlatformSettingsResponse(result.Snapshot!);
+ context.Response.Headers.ETag = ETagHelper.ToHeaderValue(response.ETag);
+ return TypedResults.Ok(response);
+ }
+
+ private static UserPreferencesResponse ToUserPreferencesResponse(UserPreferencesSnapshot snapshot)
+ {
+ var values = snapshot.Values;
+ return new UserPreferencesResponse(
+ 1,
+ snapshot.Version.ToString(CultureInfo.InvariantCulture),
+ snapshot.IsDefault,
+ values.Theme,
+ values.Density,
+ values.PrimaryNavigationCollapsed,
+ values.ContextPanelOpen,
+ values.NavigationOrder,
+ values.DashboardWidgetOrder,
+ values.HiddenDashboardWidgets,
+ values.DefaultLandingPage,
+ values.CatalogView,
+ values.ContextPanelWidth,
+ values.NavigationWidth,
+ snapshot.UpdatedAt);
+ }
+
+ private static PlatformSettingsResponse ToPlatformSettingsResponse(PlatformSettingsSnapshot snapshot)
+ {
+ var values = snapshot.Values;
+ return new PlatformSettingsResponse(
+ 1,
+ snapshot.Version.ToString(CultureInfo.InvariantCulture),
+ snapshot.IsDefault,
+ values.DisplayName,
+ values.PrimaryLocale,
+ values.TimeZone,
+ new IngestionProposalPolicyContract(
+ values.IngestionProposals.Enabled,
+ values.IngestionProposals.MaximumSourceCount,
+ values.IngestionProposals.MaximumSourceBytes,
+ values.IngestionProposals.FailedRetryLimit),
+ new BackupPolicyExpectationResponse(
+ "operator-managed",
+ values.BackupPolicyExpectation.ExpectedIntervalHours,
+ values.BackupPolicyExpectation.ScheduleDescription,
+ values.BackupPolicyExpectation.DailyRetentionCount,
+ values.BackupPolicyExpectation.WeeklyRetentionCount,
+ values.BackupPolicyExpectation.MonthlyRetentionCount,
+ values.BackupPolicyExpectation.ProtectedBackupCount,
+ values.BackupPolicyExpectation.RestoreVerificationMaxAgeDays),
+ new InventoryFreshnessPolicyContract(values.InventoryFreshnessPolicy.WarningAfterHours),
+ snapshot.UpdatedAt);
+ }
+
+ private static bool TryGetExpectedVersion(HttpContext context, out int version, out IResult? error)
+ {
+ var raw = context.Request.Headers.IfMatch.ToString();
+ if (string.IsNullOrWhiteSpace(raw))
+ {
+ version = 0;
+ error = Results.Problem(statusCode: StatusCodes.Status428PreconditionRequired,
+ title: "If-Match-header vereist.", detail: "Lees eerst de actuele ETag en stuur die mee.");
+ return false;
+ }
+
+ version = 0;
+ var token = ETagHelper.FromHeaderValue(raw);
+ if (token is null || !int.TryParse(token, NumberStyles.None, CultureInfo.InvariantCulture, out version) || version < 0)
+ {
+ error = Results.Problem(statusCode: StatusCodes.Status400BadRequest,
+ type: ProblemTypes.Validation, title: "Ongeldige If-Match-header.");
+ return false;
+ }
+
+ error = null;
+ return true;
+ }
+
+ private static Guid CurrentUserId(ClaimsPrincipal principal)
+ {
+ var raw = principal.FindFirstValue(ClaimTypes.NameIdentifier);
+ return Guid.TryParse(raw, out var id) ? id : throw new InvalidOperationException("The authenticated user ID is missing.");
+ }
+
+ private static IResult NotFound() => Results.Problem(statusCode: StatusCodes.Status404NotFound, type: ProblemTypes.NotFound);
+
+ private static IResult ValidationProblem(string detail) => Results.Problem(
+ statusCode: StatusCodes.Status422UnprocessableEntity,
+ type: ProblemTypes.Validation,
+ title: "Ongeldige instellingen.",
+ detail: detail);
+
+ private static IResult ConcurrencyProblem(string detail) => Results.Problem(
+ statusCode: StatusCodes.Status412PreconditionFailed,
+ type: ProblemTypes.ConcurrencyConflict,
+ title: "Versieconflict.",
+ detail: detail);
+}
diff --git a/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/SearchEndpoints.cs b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/SearchEndpoints.cs
index d9db901..65381d4 100644
--- a/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/SearchEndpoints.cs
+++ b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/SearchEndpoints.cs
@@ -1,4 +1,5 @@
using ItWorx.KnowledgeHub.Api.Authorization;
+using ItWorx.KnowledgeHub.Api.Security;
using ItWorx.KnowledgeHub.Application.Identity;
using ItWorx.KnowledgeHub.Application.Knowledge.UseCases;
using ItWorx.KnowledgeHub.Contracts.Errors;
@@ -16,6 +17,7 @@ public static class SearchEndpoints
{
app.MapGet("/api/v1/search", SearchAsync)
.WithTags("Search")
+ .RequireRateLimiting(RateLimitPolicies.Search)
.RequireContentAccess(WorkspaceRole.Reader, Scopes.KnowledgeSearch);
return app;
diff --git a/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/UnifiedSearchEndpoints.cs b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/UnifiedSearchEndpoints.cs
new file mode 100644
index 0000000..2e7e23a
--- /dev/null
+++ b/src/backend/ItWorx.KnowledgeHub.Api/Endpoints/UnifiedSearchEndpoints.cs
@@ -0,0 +1,92 @@
+using ItWorx.KnowledgeHub.Api.Authorization;
+using ItWorx.KnowledgeHub.Api.Security;
+using ItWorx.KnowledgeHub.Application.Identity;
+using ItWorx.KnowledgeHub.Application.UnifiedSearch;
+using ItWorx.KnowledgeHub.Contracts.Errors;
+using ItWorx.KnowledgeHub.Contracts.UnifiedSearch;
+using Microsoft.AspNetCore.Http.HttpResults;
+using Microsoft.AspNetCore.Mvc;
+
+namespace ItWorx.KnowledgeHub.Api.Endpoints;
+
+public static class UnifiedSearchEndpoints
+{
+ public static IEndpointRouteBuilder MapUnifiedSearchEndpoints(this IEndpointRouteBuilder app)
+ {
+ // Unified search includes projects and infrastructure services. Keep it on
+ // the human workspace-reader policy so a bearer token with only
+ // knowledge:search cannot gain either resource's scope through aggregation.
+ app.MapGet("/api/v1/unified-search", SearchAsync)
+ .WithTags("Search")
+ .RequireRateLimiting(RateLimitPolicies.Search)
+ .RequireAuthorization(AuthorizationPolicies.RequireReader);
+
+ return app;
+ }
+
+ private static async Task, ProblemHttpResult>> SearchAsync(
+ [FromQuery] string? q,
+ [FromQuery] string? tag,
+ [FromQuery] string? kind,
+ [FromQuery] string? cursor,
+ [FromQuery] int? limit,
+ IWorkspaceRepository workspaces,
+ UnifiedSearchUseCase useCase,
+ CancellationToken cancellationToken)
+ {
+ if (!TryParseKind(kind, out var searchKind))
+ {
+ return TypedResults.Problem(
+ statusCode: StatusCodes.Status422UnprocessableEntity,
+ type: ProblemTypes.Validation,
+ detail: "Kind must be one of: knowledge, projects, services, runbooks, decisions.");
+ }
+
+ var workspaceId = await workspaces.GetDefaultWorkspaceIdAsync(cancellationToken);
+ if (workspaceId is null)
+ {
+ return TypedResults.Problem(statusCode: StatusCodes.Status404NotFound, type: ProblemTypes.NotFound);
+ }
+
+ var result = await useCase.ExecuteAsync(workspaceId.Value, q, tag, searchKind, cursor, limit ?? 25, cancellationToken);
+ IReadOnlyList items = result.Items
+ .Select(hit => new UnifiedSearchResultItem(
+ ToPublicKind(hit.Kind), hit.Id, hit.Title, hit.Summary, hit.Snippet, hit.UpdatedAt, hit.Href))
+ .ToArray();
+ var counts = new UnifiedSearchCounts(
+ result.Counts.Knowledge, result.Counts.Projects, result.Counts.Services,
+ result.Counts.Runbooks, result.Counts.Decisions);
+
+ return TypedResults.Ok(new UnifiedSearchResponse(items, counts, result.NextCursor));
+ }
+
+ private static bool TryParseKind(string? value, out UnifiedSearchKind? kind)
+ {
+ kind = null;
+ if (value is null)
+ {
+ return true;
+ }
+
+ kind = value.Trim().ToLowerInvariant() switch
+ {
+ "knowledge" => UnifiedSearchKind.Knowledge,
+ "projects" => UnifiedSearchKind.Projects,
+ "services" => UnifiedSearchKind.Services,
+ "runbooks" => UnifiedSearchKind.Runbooks,
+ "decisions" => UnifiedSearchKind.Decisions,
+ _ => null
+ };
+ return kind is not null;
+ }
+
+ private static string ToPublicKind(string kind) => kind switch
+ {
+ "knowledge" => "knowledge",
+ "project" => "projects",
+ "service" => "services",
+ "runbook" => "runbooks",
+ "decision" => "decisions",
+ _ => throw new InvalidOperationException($"Unexpected unified search kind '{kind}'.")
+ };
+}
diff --git a/src/backend/ItWorx.KnowledgeHub.Api/ItWorx.KnowledgeHub.Api.csproj b/src/backend/ItWorx.KnowledgeHub.Api/ItWorx.KnowledgeHub.Api.csproj
index 35869ec..b06ba0f 100644
--- a/src/backend/ItWorx.KnowledgeHub.Api/ItWorx.KnowledgeHub.Api.csproj
+++ b/src/backend/ItWorx.KnowledgeHub.Api/ItWorx.KnowledgeHub.Api.csproj
@@ -4,6 +4,11 @@
ItWorx.KnowledgeHub.Api
false
+
+
+
+
+
diff --git a/src/backend/ItWorx.KnowledgeHub.Api/Operations/RestoreModeRunner.cs b/src/backend/ItWorx.KnowledgeHub.Api/Operations/RestoreModeRunner.cs
index 27023fa..84a849d 100644
--- a/src/backend/ItWorx.KnowledgeHub.Api/Operations/RestoreModeRunner.cs
+++ b/src/backend/ItWorx.KnowledgeHub.Api/Operations/RestoreModeRunner.cs
@@ -21,13 +21,24 @@ public static class RestoreModeRunner
public static async Task RunAsync(string[] args, IServiceProvider services, TextWriter output, CancellationToken cancellationToken)
{
var backupId = GetArgValue(args, "--backup-id");
- var targetConnection = GetArgValue(args, "--target-connection");
+ var targetConnectionFile = GetArgValue(args, "--target-connection-file");
var targetAttachments = GetArgValue(args, "--target-attachments");
- if (backupId is null || targetConnection is null || targetAttachments is null)
+ if (HasArg(args, "--target-connection") || backupId is null || targetConnectionFile is null || targetAttachments is null)
{
await output.WriteLineAsync(
- "Gebruik: --restore --backup-id= --target-connection= --target-attachments=");
+ "Gebruik: --restore --backup-id= --target-connection-file= --target-attachments=");
+ return 2;
+ }
+
+ string targetConnection;
+ try
+ {
+ targetConnection = await ReadTargetConnectionAsync(targetConnectionFile, cancellationToken);
+ }
+ catch (Exception exception) when (exception is not OperationCanceledException)
+ {
+ await output.WriteLineAsync("Herstel niet gestart: de doelconnectie kon niet veilig worden gelezen.");
return 2;
}
@@ -40,7 +51,9 @@ public static class RestoreModeRunner
}
catch (Exception exception) when (exception is not OperationCanceledException)
{
- await output.WriteLineAsync($"Herstel mislukt: {exception.Message}");
+ // Provider messages may include the target connection string. Do not
+ // print exception text in this maintenance command's output.
+ await output.WriteLineAsync("Herstel mislukt; gevoelige foutdetails zijn niet weergegeven.");
return 1;
}
@@ -63,7 +76,47 @@ public static class RestoreModeRunner
private static string? GetArgValue(string[] args, string name)
{
var prefix = name + "=";
- var match = args.FirstOrDefault(argument => argument.StartsWith(prefix, StringComparison.Ordinal));
- return match?[prefix.Length..];
+ var matches = args.Where(argument => argument.StartsWith(prefix, StringComparison.Ordinal)).ToArray();
+ return matches.Length == 1 && matches[0].Length > prefix.Length
+ ? matches[0][prefix.Length..]
+ : null;
+ }
+
+ private static bool HasArg(string[] args, string name) =>
+ args.Any(argument => argument.Equals(name, StringComparison.Ordinal)
+ || argument.StartsWith(name + "=", StringComparison.Ordinal));
+
+ private static async Task ReadTargetConnectionAsync(string path, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrWhiteSpace(path)) throw new IOException("Missing connection file.");
+ var fullPath = Path.GetFullPath(path);
+ var attributes = File.GetAttributes(fullPath);
+ if ((attributes & (FileAttributes.Directory | FileAttributes.ReparsePoint)) != 0)
+ throw new IOException("Connection file must be a regular file.");
+
+ if (!OperatingSystem.IsWindows())
+ {
+ var mode = File.GetUnixFileMode(fullPath);
+ const UnixFileMode groupOrOtherAccess = UnixFileMode.GroupRead | UnixFileMode.GroupWrite
+ | UnixFileMode.GroupExecute | UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute;
+ if ((mode & groupOrOtherAccess) != 0 || (mode & UnixFileMode.UserRead) == 0)
+ throw new IOException("Connection file permissions are too broad.");
+ }
+
+ await using var stream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read);
+ if (stream.Length is <= 0 or > 16_384) throw new IOException("Connection file is empty or too large.");
+ var bytes = new byte[checked((int)stream.Length)];
+ await stream.ReadExactlyAsync(bytes, cancellationToken);
+ var extra = new byte[1];
+ if (await stream.ReadAsync(extra, cancellationToken) != 0)
+ throw new IOException("Connection file changed while being read.");
+
+ var connectionString = new System.Text.UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true)
+ .GetString(bytes)
+ .TrimStart('\uFEFF')
+ .TrimEnd('\r', '\n');
+ if (string.IsNullOrWhiteSpace(connectionString) || connectionString.Contains('\r') || connectionString.Contains('\n'))
+ throw new IOException("Connection file contents are invalid.");
+ return connectionString;
}
}
diff --git a/src/backend/ItWorx.KnowledgeHub.Api/Program.cs b/src/backend/ItWorx.KnowledgeHub.Api/Program.cs
index 30ff961..aa67579 100644
--- a/src/backend/ItWorx.KnowledgeHub.Api/Program.cs
+++ b/src/backend/ItWorx.KnowledgeHub.Api/Program.cs
@@ -13,23 +13,31 @@ using ItWorx.KnowledgeHub.Application.Identity.UseCases;
using ItWorx.KnowledgeHub.Application.Infrastructure.UseCases;
using ItWorx.KnowledgeHub.Application.Ingestion;
using ItWorx.KnowledgeHub.Application.Ingestion.UseCases;
+using ItWorx.KnowledgeHub.Application.Jobs;
+using ItWorx.KnowledgeHub.Application.Jobs.UseCases;
using ItWorx.KnowledgeHub.Application.Knowledge.UseCases;
using ItWorx.KnowledgeHub.Application.Migration.UseCases;
using ItWorx.KnowledgeHub.Application.Operations.UseCases;
+using ItWorx.KnowledgeHub.Application.Platform;
+using ItWorx.KnowledgeHub.Application.Platform.UseCases;
using ItWorx.KnowledgeHub.Application.Projects.UseCases;
using ItWorx.KnowledgeHub.Application.Runbooks.UseCases;
+using ItWorx.KnowledgeHub.Application.UnifiedSearch;
using ItWorx.KnowledgeHub.Contracts.SystemInfo;
using ItWorx.KnowledgeHub.Domain.Automation;
using ItWorx.KnowledgeHub.Domain.Identity;
using ItWorx.KnowledgeHub.Infrastructure;
using ItWorx.KnowledgeHub.Infrastructure.Operations;
using ItWorx.KnowledgeHub.Infrastructure.Persistence;
+using ItWorx.KnowledgeHub.Infrastructure.Platform;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Resources;
var timeProvider = TimeProvider.System;
var startedAt = timeProvider.GetUtcNow();
@@ -58,6 +66,38 @@ if (builder.Environment.IsProduction())
}
builder.Services.AddSingleton(timeProvider);
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped(provider => provider.GetRequiredService());
+builder.Services.AddScoped(provider => provider.GetRequiredService());
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddSingleton(provider =>
+{
+ var options = new BackgroundJobsOptions();
+ provider.GetRequiredService().GetSection("KnowledgeHub:Jobs").Bind(options);
+ options.Validate();
+ return options;
+});
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddOpenTelemetry()
+ .ConfigureResource(resource => resource.AddService("itworx-knowledge-hub"))
+ .WithMetrics(metrics =>
+ {
+ metrics.AddAspNetCoreInstrumentation()
+ .AddMeter("ItWorx.KnowledgeHub.Ingestion", "ItWorx.KnowledgeHub.Platform", "System.Runtime");
+ // OTLP is enabled only by an explicit operator endpoint. No default cloud destination.
+ if (!string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]))
+ {
+ metrics.AddOtlpExporter();
+ }
+ });
builder.Services.AddSingleton(_ => new Meter("ItWorx.KnowledgeHub.Ingestion"));
builder.Services.AddSingleton();
builder.Services.AddProblemDetails(options =>
@@ -208,6 +248,10 @@ builder.Services.AddRateLimiter(options =>
CreateRemoteIpPartition(context, permitLimit: 10));
options.AddPolicy(RateLimitPolicies.SourceUpload, context =>
CreateRemoteIpPartition(context, permitLimit: 5));
+ options.AddPolicy(RateLimitPolicies.Search, context =>
+ CreateRemoteIpPartition(context, permitLimit: 60));
+ options.AddPolicy(RateLimitPolicies.JobCreation, context =>
+ CreateRemoteIpPartition(context, permitLimit: 10));
});
// --- Use cases (composition root) -------------------------------------------------------
@@ -228,6 +272,8 @@ builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddHostedService();
+builder.Services.AddHostedService();
+builder.Services.AddHostedService();
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
@@ -311,6 +357,7 @@ builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
+builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
@@ -495,6 +542,7 @@ app.MapGet(
async (
IHostEnvironment environment,
IConfiguration configuration,
+ IPlatformSettingsReader platformSettings,
ReleaseManifestReader releaseManifestReader,
KnowledgeHubDbContext db,
CancellationToken cancellationToken) =>
@@ -502,8 +550,9 @@ app.MapGet(
var assembly = Assembly.GetExecutingAssembly();
var version = assembly.GetName().Version?.ToString() ?? "0.0.0";
var commit = configuration["Build:Commit"] ?? "development";
- var locale = configuration["KnowledgeHub:PrimaryLocale"] ?? "nl-BE";
- var timeZone = configuration["KnowledgeHub:TimeZone"] ?? "Europe/Brussels";
+ var settings = (await platformSettings.GetEffectiveAsync(cancellationToken)).Values;
+ var locale = settings.PrimaryLocale;
+ var timeZone = settings.TimeZone;
var uptimeSeconds = Math.Max(
0,
@@ -549,10 +598,10 @@ app.MapGet(
return Results.Ok(
new SystemInfoResponse(
- "ItWorx Knowledge Hub",
- version,
+ settings.DisplayName,
+ manifest?.SemanticVersion ?? version,
commit,
- "m7",
+ migrationRange?.Split("..").Last() ?? "Unknown",
environment.EnvironmentName,
locale,
timeZone,
@@ -568,6 +617,7 @@ app.MapPagesEndpoints();
app.MapDraftsEndpoints();
app.MapTemplatesEndpoints();
app.MapSearchEndpoints();
+app.MapUnifiedSearchEndpoints();
app.MapRagPublicationEndpoints();
app.MapAttachmentsEndpoints();
app.MapIngestionProposalsEndpoints();
@@ -578,6 +628,10 @@ app.MapDecisionsEndpoints();
app.MapRunbooksEndpoints();
app.MapServiceAccountsEndpoints();
app.MapBackupsEndpoints();
+app.MapAdminAuditEndpoints();
+app.MapDataSourcesEndpoints();
+app.MapPlatformEndpoints();
+app.MapJobsEndpoints();
app.MapInfrastructureEndpoints();
app.MapActivityEndpoints();
app.MapWorkspaceEndpoints();
diff --git a/src/backend/ItWorx.KnowledgeHub.Api/Security/RateLimitPolicies.cs b/src/backend/ItWorx.KnowledgeHub.Api/Security/RateLimitPolicies.cs
index a181bfd..935c434 100644
--- a/src/backend/ItWorx.KnowledgeHub.Api/Security/RateLimitPolicies.cs
+++ b/src/backend/ItWorx.KnowledgeHub.Api/Security/RateLimitPolicies.cs
@@ -8,4 +8,6 @@ public static class RateLimitPolicies
public const string Login = "login";
public const string Recovery = "recovery";
public const string SourceUpload = "source-upload";
+ public const string Search = "search";
+ public const string JobCreation = "job-creation";
}
diff --git a/src/backend/ItWorx.KnowledgeHub.Api/Security/StrictAntiforgeryEndpointFilter.cs b/src/backend/ItWorx.KnowledgeHub.Api/Security/StrictAntiforgeryEndpointFilter.cs
new file mode 100644
index 0000000..05e8906
--- /dev/null
+++ b/src/backend/ItWorx.KnowledgeHub.Api/Security/StrictAntiforgeryEndpointFilter.cs
@@ -0,0 +1,29 @@
+using ItWorx.KnowledgeHub.Contracts.Errors;
+using Microsoft.AspNetCore.Antiforgery;
+
+namespace ItWorx.KnowledgeHub.Api.Security;
+
+/// CSRF validation that cannot be bypassed by a caller-supplied Authorization
+/// header. Used by routes that are specifically cookie-authenticated.
+public sealed class StrictAntiforgeryEndpointFilter(IAntiforgery antiforgery) : IEndpointFilter
+{
+ public async ValueTask