From b9a6d6d9952cf1cafa3f000a98981f668822b17c Mon Sep 17 00:00:00 2001
From: Magic <131926685+magiccodingman@users.noreply.github.com>
Date: Sat, 18 Jul 2026 20:28:50 -0400
Subject: [PATCH 01/10] Bootstrap MagicSettings implementation
---
.github/workflows/bootstrap.yml | 54 +++++++++++++++++++++++++++++++++
.magicsettings-source.part-00 | 1 +
.magicsettings-source.part-01 | 1 +
.magicsettings-source.part-02 | 1 +
.magicsettings-source.part-s00 | 1 +
.magicsettings-source.part-s01 | 1 +
.magicsettings-source.part-s02 | 1 +
.magicsettings-source.part-s03 | 1 +
.magicsettings-source.part-s04 | 1 +
.magicsettings-source.part-s05 | 1 +
.magicsettings-source.part-s06 | 1 +
.magicsettings-source.part-s07 | 1 +
.magicsettings-source.part-s08 | 1 +
.magicsettings-source.part-s09 | 1 +
.magicsettings-source.part-s10 | 1 +
.magicsettings-source.part-s11 | 1 +
.magicsettings-source.part-s12 | 1 +
17 files changed, 70 insertions(+)
create mode 100644 .github/workflows/bootstrap.yml
create mode 100644 .magicsettings-source.part-00
create mode 100644 .magicsettings-source.part-01
create mode 100644 .magicsettings-source.part-02
create mode 100644 .magicsettings-source.part-s00
create mode 100644 .magicsettings-source.part-s01
create mode 100644 .magicsettings-source.part-s02
create mode 100644 .magicsettings-source.part-s03
create mode 100644 .magicsettings-source.part-s04
create mode 100644 .magicsettings-source.part-s05
create mode 100644 .magicsettings-source.part-s06
create mode 100644 .magicsettings-source.part-s07
create mode 100644 .magicsettings-source.part-s08
create mode 100644 .magicsettings-source.part-s09
create mode 100644 .magicsettings-source.part-s10
create mode 100644 .magicsettings-source.part-s11
create mode 100644 .magicsettings-source.part-s12
diff --git a/.github/workflows/bootstrap.yml b/.github/workflows/bootstrap.yml
new file mode 100644
index 0000000..8d2dd1c
--- /dev/null
+++ b/.github/workflows/bootstrap.yml
@@ -0,0 +1,54 @@
+name: Bootstrap MagicSettings implementation
+
+on:
+ push:
+ branches:
+ - agent/build-magicsettings-runtime
+
+permissions:
+ contents: write
+
+jobs:
+ materialize:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out branch
+ uses: actions/checkout@v4
+
+ - name: Rebuild source tree
+ shell: bash
+ run: |
+ set -euo pipefail
+ cat .magicsettings-source.part-* > /tmp/magicsettings-source.tar.gz.b64
+ base64 --decode /tmp/magicsettings-source.tar.gz.b64 > /tmp/magicsettings-source.tar.gz
+ rm -rf MagicSettings wiki README.md .gitignore .github
+ tar -xzf /tmp/magicsettings-source.tar.gz -C .
+ rm -f .magicsettings-source.part-*
+ rm -f .github/workflows/bootstrap.yml
+
+ - name: Set up .NET 10
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 10.0.x
+
+ - name: Restore
+ working-directory: MagicSettings
+ run: dotnet restore MagicSettings.sln
+
+ - name: Build
+ working-directory: MagicSettings
+ run: dotnet build MagicSettings.sln --configuration Release --no-restore
+
+ - name: Test
+ working-directory: MagicSettings
+ run: dotnet test MagicSettings.sln --configuration Release --no-build --logger "console;verbosity=normal"
+
+ - name: Commit implementation
+ shell: bash
+ run: |
+ set -euo pipefail
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ git add -A
+ git commit -m "Build MagicSettings runtime"
+ git push origin HEAD:agent/build-magicsettings-runtime
diff --git a/.magicsettings-source.part-00 b/.magicsettings-source.part-00
new file mode 100644
index 0000000..2285b40
--- /dev/null
+++ b/.magicsettings-source.part-00
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/.magicsettings-source.part-01 b/.magicsettings-source.part-01
new file mode 100644
index 0000000..19f066d
--- /dev/null
+++ b/.magicsettings-source.part-01
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/.magicsettings-source.part-02 b/.magicsettings-source.part-02
new file mode 100644
index 0000000..f038300
--- /dev/null
+++ b/.magicsettings-source.part-02
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s00 b/.magicsettings-source.part-s00
new file mode 100644
index 0000000..e5b1f30
--- /dev/null
+++ b/.magicsettings-source.part-s00
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s01 b/.magicsettings-source.part-s01
new file mode 100644
index 0000000..0e1eed5
--- /dev/null
+++ b/.magicsettings-source.part-s01
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s02 b/.magicsettings-source.part-s02
new file mode 100644
index 0000000..17209e5
--- /dev/null
+++ b/.magicsettings-source.part-s02
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s03 b/.magicsettings-source.part-s03
new file mode 100644
index 0000000..89b2969
--- /dev/null
+++ b/.magicsettings-source.part-s03
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s04 b/.magicsettings-source.part-s04
new file mode 100644
index 0000000..5281a9b
--- /dev/null
+++ b/.magicsettings-source.part-s04
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s05 b/.magicsettings-source.part-s05
new file mode 100644
index 0000000..ebabf68
--- /dev/null
+++ b/.magicsettings-source.part-s05
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s06 b/.magicsettings-source.part-s06
new file mode 100644
index 0000000..a7839d8
--- /dev/null
+++ b/.magicsettings-source.part-s06
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s07 b/.magicsettings-source.part-s07
new file mode 100644
index 0000000..150c834
--- /dev/null
+++ b/.magicsettings-source.part-s07
@@ -0,0 +1 @@
+UrBaypwixTVbD0jHsmFcKYnjsTXyKiyIEXNYqWT5RqCSik4jjvkV0lacoXpful5Ll+whmyZA0egPljIijSDNB35fnMBZhEd5lGXiFMLO0uyiCAUZOaCJoAVMcVJfREPBGfWk4zkMION6ZIL9EsfPNbFvPTL+DzCdXyfIU1Ndy1wM8REgZ4KQ272W6FxkFVH4DaRgBmQEMDaeT8DIJAY8IhMVYA69p9bA+niZBoILzLD70ZBO+Ci4mKAFSXBEolFJTthhxJdGPdITIx/RITvX+eTyEgKZoAcQqU7mLbWaY/k2LWqxfmmqLlMIqoa10Jt04RtybsBop7gIyRCjkWkYKYue4PtwcP73//X/odoW1hDUhz4vYBKD1ugufCVtItN/g/m8ex5g+vN/VciE8/P/Pi7P/N85D1B7/q8W/P/W1ufxX/dyfR8c6KmXoGkTiiONhhIPxIkDMF55L92OY8OY1kMpZkz1kTQHwhmic8Rwpwvn11WSY6Kh4JAKo7LUxXf7FGVDUkBKHwL2l2aKakWgdnzCd8dA9OiIwwIItPUOHozW0UkkG2VzEMPgBLB5ACdY1mRYTlMau2Qo4XsKSmhjbFEHzSkHDuk1A4ZQRrkp9fapUtNiR3LG8+DQbPFJQE4CMbXLEc4d5dsh9acYb93T0ELQeyFpQ+A015Jr/W9oJMMz1EDsvMDpu96/fX14sLd/enjwDgZgjwbPoCigHCDIKPLR4CX2KcrlaS/ZBzcI+RGM/bVkOsgzhbrMqgUJQYpn8p5iBdAsAXwlOv51r2L1UcySBn0GR8gEjt0hSfhCPi9wwvD1TMJ+xRZGqBmMHewdH1n8CYb7kaOpWcy7yIBrOUe+7oJcPlHXAxoDcJf56vuf6f9XkfvkVUP/VzbWC/L/8jz/1/1c3xe8HChAiw2BzkNT40qqUVCY0eIhPyqx2VAnRYADsLa0s4XW4GkDIsKsgQ8TAgH34KzoS7mgB2o+6bAstzaAFCEU4PffBydgZwxgjz54sBwGb59RQ8n+GPfetS/pxiLfEOt74cGKKLinv0rUIP0kzgTwYX/XNloU4wur4gWLLBlU9l3b2tSL+gm+uiZehfxJwSVlXZT4xOikiEAP6BX8rg2y3KIuhI636EOD1ayLasww7TzuE5Leu7bhVEJlYVwOPwoZFAeCZw5Utm9fWyfru7Z90uLLizA0GXjh06HY5+8oh20YHniODdTPc/m2xi5+1/ZhituFFMYke1UYrh32+wVMUqyniFxmvaSB7HhUPkuibK1qHBsVDAg8CDh3vGt7IHHwq954ebN4GYICvu1GW5ovekJv6R1vhAYuPV/QDr5T4tXrvlTu6Y21+H3D3EpK3QWxjqJ/gfu+190E3y2zTbk1aIMlveYe5O/aBQUnrYlXyi5k4AWLn7go6jnjd22v4ERjF3cnGPc5SAXdJCfeZJCM5Zs5P3dXM6om4svrd21zAX8t+s/nf5FQ3uE3as7/tbVNV/+7troyj/++l+v7wDk0CyhQKk5Vhdeiu/Eg7iXECJDlDP1XpEnNdNUErz+URDhM1QILZ/8b8iwxkGytUzV0BSiuUghPv8Xne7o+zvf8hB6Lo+oSUmQ/oNzcKDVJdTHXAKHAVndJd/ziWmeLhko67PSUB9s7HA+uYon11496mHHu2rjF0eBloP/L9uM3eXwaD0Yg2rTbCxAvjtmnVWMwHvkGepRcBG3qUXiC83P4ORkvcNPIrPvIRIQHpTm4p0IEmHIrQgdUjh8bJSPB2w1jlthJt51jxJgMSJZztYW8Fuu5TWW26d8amTCciEgMfBdND1vJe/EimoyBzeNec/CAkBGNukJgwCDOXHtJuZpwxCMhpHbBcb2eSJxLxUcaYJiw6ITMCRwa8FViASjITNUMTjQhze8Q0kgir8aYTzPl5bnBzes6RzvHiXiwwjQmzoPNMKCUtcb5g5Im6IFJA254YymV/t+p5UXNhNKJCNZ5CF67/YJQHT74B8wfh9GDi3ZPKamN
\ No newline at end of file
diff --git a/.magicsettings-source.part-s08 b/.magicsettings-source.part-s08
new file mode 100644
index 0000000..8e266bb
--- /dev/null
+++ b/.magicsettings-source.part-s08
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s09 b/.magicsettings-source.part-s09
new file mode 100644
index 0000000..ac154d7
--- /dev/null
+++ b/.magicsettings-source.part-s09
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s10 b/.magicsettings-source.part-s10
new file mode 100644
index 0000000..079ede2
--- /dev/null
+++ b/.magicsettings-source.part-s10
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s11 b/.magicsettings-source.part-s11
new file mode 100644
index 0000000..451d12a
--- /dev/null
+++ b/.magicsettings-source.part-s11
@@ -0,0 +1 @@
+qkbgUtllUT0plWps+kkIyDCSiLuGz2yxU6YPqTbVsVqSdWecBLWHyrlh/IkShJBjoYZzQuXbRDuKdBytZAHwi7R1BOHbN7K7yJQ3ETQMQBDI5UNDfqCR2EAp0InD1bslsLMKRJkjCOyUQo9Pd96DJ0Suc9saOLNFvCHUrSldJ/n4RGTPpJWp8+rpbLowzIUcL9LbYkBuuaJDA9TqqZB6AxLNzZLBA9GndLUxe+soqBRyJulGEo9SfgqxaCk/g51dJ/Tsv59+Gqbjn37S4EZdNx0AmCYlEp2GpxCNGoCPFKI7+C3a4FOCWHds0c8NDDwd1KcRsjDvAMNmiaUqjspFFTxXAG4CuzF61RK8sdNsRLSA1QCogXJccgx87MWobdWggkgYTFLDUTqMV8Uhvd0PkALgwYPfFZKW+GuS4Sr9XdxdXFwM+L/il4us9bs0napuOwYHHUGZ8G9T5esEFKHnAxfj7AZKYW5nSegY3JERkhx6GimBvX4PTiReopoS1ZZFUq+b1oNFnYAZpgCR0nhOz695tiU6HAxo7v84o4D9Dhp3ZXHuFBeCXAFwDmgDQqfY8SyGRBvOAJCDiLcFwKvhTJElSnJr+CFzntRYdCzvEhuLjpcr7IDxtWFO+B1X0/+YJN0P5Ohn+tqRn5jdqiMJL8xwKrMC1COX2xCf3gAcnh7ouYhYj26AyTPRsE/RtQfiuQS23iziR4E2gZHlG18MXUoQGI2RKMjWc7s0XC+BmhZb3SkUdGLqtAtHyyp6Y7/J/hbN2iJj8EAFJP7naQRiLG8HgqtdrfqqwoWe7rs69q/0y4DnXHwopisb8kCOCNMu99Uh4Z0tQGe7I+oX4nvLH1OGRvq7OluoZOHtypBJs/WqF5BeAfdjZXYF3LaCqRNVyu2KGxuAvMVtwDRvmwGZF0mW4/nCEAO5hyUkLC4+3wwGOgyeI2IRaFA1z6h8i6XvTmRkM9C27A5+tJgMIe8YGQd0yL+BgU1edP1rSlzM+AMEaYaIlRorzsVJQtYyd3khZtTAFznHbiKuGnoakNO4gZBWBSYHE+GBpNVItC5wXAkyrQYJky9m9UBypwVfZcn3F+LkMPYM2DIGE448hnryclQQdqUoZR5sMpwf8uyS4oyCoU1VZiN2xXDAbBGbWJU2MmioYDMferoxP84Qy112diapxrbhHM9IdTx+YKCPRhSsXYSUo1WnWHvlTl5wi5br8DUDoulsZy9JBJGgn5aDAHDeDKwGdoVC+oUrohJyS1tCzFudmxNj297x9j6xhUVO7QjCs3Kbl9KRkFOBxYX018nwnyT6btVlGPhNNFTwC47IZLIBqvMLdnKAj2nSC5ASOezARwM51cxCuk/gI4pqMllUIP6pcoDVqHV3mOFU5xvVGR6i7IPONeokJXjPCUnJB5jWgwZ0qRtX5mmWT1P+ayVwTEuqLntY1eYFR9qngqYp7mhHske+kuo7UE7mWXALYu750yj/wB2h5B5tNXP+9jHGEC5w8a8+zPfdjBpBIceGmCO2v7mrhGsLX8fALrStA1bPIrATDvtgP4SXnQLYC6J5qg9Hg5GYxhDAgViGowgUT3MGYric5rwSG2vvaOt5fBl1r19EkN3ml9jlDCs++yIaTqL+a5RKXvNhaX6cTn49PYI0UGhTD345+wQVz5Y3mFimtlJGOwtDZ9j1m8m/VOT0ryFohA8lrarCM1lGDX8CfRQppUiEpTRakNHWQFRWGFIRe2BZyZnxxMDgINkGxtpWLtsqfCvN+JYbV4p6NQiplnT5lZMniNQxgIdE3nbKE52jX0zsKyvUqiTgCQZFyl+Hsq5tnAeQPt5kSbt1NR6P8q2HD7kJYUyHW9hNBw+RxVtUhgjYZYoOvcpkwsK4R2iRBSLHVR4ji7ANTAoKyZAjC9sQPovHch3xg8c2Bd83athp6/w4Zs3qAGIy0EtylHzjnuyyDfqRUSYCNx8BAKRbGQ4oQYMpoGcxurx3Cr56liuiAcbBnuIYBSU1
\ No newline at end of file
diff --git a/.magicsettings-source.part-s12 b/.magicsettings-source.part-s12
new file mode 100644
index 0000000..62d4865
--- /dev/null
+++ b/.magicsettings-source.part-s12
@@ -0,0 +1 @@
+cxz1VJHu4PkR6KJABZdrZmJx0Z4MiUUYF58gvql6HiwuXovxLpSSjG3xCSrfENyw7JmDk/u+tHHv7WRqrPgs0UW46iMz4SQEXJoZ2JDdFkQlLwwmQgSIDRY8RZhXwyFWChURO53KfHs6c5kZIkl4WcOeFAuEMIIj+Z4Bv+w0wJAM07wDGb3jz+/adu5oTGNZyMFbkl+Sk82W5+H15aWR6VBLcvHWQZnj66V4h/x+dSreQh5dzmIMMbRcgT+vrpkZF4uVpsc91rlhsWBZItqytLByFEpzw3rSo8ogQDdHqp0Flcs1zXmqq/UlOuWnbpbe54JMDnM4JUaYuJ1/Byvh0jzSZH7Nr/k1v+bX/Jpf82t+za/5Nb/m1/yaX/Nrfs2v6a7/HyYscTAAOAQA
\ No newline at end of file
From 1627243d61330d7b307c9b61556db7e0c0b6e8c1 Mon Sep 17 00:00:00 2001
From: Magic <131926685+magiccodingman@users.noreply.github.com>
Date: Sat, 18 Jul 2026 20:29:54 -0400
Subject: [PATCH 02/10] Enable pull request bootstrap validation
---
.github/workflows/bootstrap.yml | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/bootstrap.yml b/.github/workflows/bootstrap.yml
index 8d2dd1c..3657bd4 100644
--- a/.github/workflows/bootstrap.yml
+++ b/.github/workflows/bootstrap.yml
@@ -4,6 +4,9 @@ on:
push:
branches:
- agent/build-magicsettings-runtime
+ pull_request:
+ branches:
+ - main
permissions:
contents: write
@@ -12,8 +15,10 @@ jobs:
materialize:
runs-on: ubuntu-latest
steps:
- - name: Check out branch
+ - name: Check out source branch
uses: actions/checkout@v4
+ with:
+ ref: ${{ github.head_ref || github.ref_name }}
- name: Rebuild source tree
shell: bash
From a3b5689be9a66e1e139c30d8a892594db201a3a2 Mon Sep 17 00:00:00 2001
From: Magic <131926685+magiccodingman@users.noreply.github.com>
Date: Sun, 19 Jul 2026 00:36:48 -0400
Subject: [PATCH 03/10] Build MagicSettings runtime and node authentication
---
.github/workflows/bootstrap.yml | 59 ---
.github/workflows/ci.yml | 25 +
.gitignore | 19 +-
.magicsettings-source.part-00 | 1 -
.magicsettings-source.part-01 | 1 -
.magicsettings-source.part-02 | 1 -
.magicsettings-source.part-s00 | 1 -
.magicsettings-source.part-s01 | 1 -
.magicsettings-source.part-s02 | 1 -
.magicsettings-source.part-s03 | 1 -
.magicsettings-source.part-s04 | 1 -
.magicsettings-source.part-s05 | 1 -
.magicsettings-source.part-s06 | 1 -
.magicsettings-source.part-s07 | 1 -
.magicsettings-source.part-s08 | 1 -
.magicsettings-source.part-s09 | 1 -
.magicsettings-source.part-s10 | 1 -
.magicsettings-source.part-s11 | 1 -
.magicsettings-source.part-s12 | 1 -
MagicSettings/Directory.Build.props | 12 +
.../MagicSettings.Server.csproj | 17 +-
.../MagicSettings.Server/Source.01.cs | 436 ++++++++++++++++++
.../MagicSettings.Server/Source.02.cs | 164 +++++++
.../ConfigurationContracts.cs | 73 +++
.../ControlPlaneContracts.cs | 58 +++
.../MagicSettings.Share.csproj | 13 +-
.../MagicSettings.Share/ProofCodec.cs | 26 ++
.../MagicSettings.Share/SecretProof.cs | 13 +
.../MagicSettings.Share/SecurityContracts.cs | 80 ++++
.../MagicSettings.Share/SyncProof.cs | 50 ++
.../MagicSettings.Tests.csproj | 45 +-
.../MagicSettings.Tests/Source.01.cs | 256 ++++++++++
.../MagicSettings.Tests/Source.02.cs | 378 +++++++++++++++
.../MagicSettings.Tests/UnitTest1.cs | 15 -
MagicSettings/MagicSettings.sln | 52 ++-
.../MagicSettings/MagicSettings.csproj | 17 +-
MagicSettings/MagicSettings/Source.01.cs | 163 +++++++
MagicSettings/MagicSettings/Source.02.cs | 379 +++++++++++++++
MagicSettings/MagicSettings/Source.03.cs | 345 ++++++++++++++
MagicSettings/MagicSettings/Source.04.cs | 423 +++++++++++++++++
MagicSettings/MagicSettings/Source.05.cs | 414 +++++++++++++++++
MagicSettings/MagicSettings/Source.06.cs | 160 +++++++
MagicSettings/MagicSettings/Source.07.cs | 405 ++++++++++++++++
README.md | 173 ++++++-
wiki/README.md | 22 +
wiki/configuration.md | 70 +++
wiki/control-plane.md | 58 +++
wiki/getting-started.md | 32 ++
wiki/migrations.md | 20 +
wiki/secrets-and-security.md | 19 +
wiki/testing.md | 11 +
51 files changed, 4344 insertions(+), 174 deletions(-)
delete mode 100644 .github/workflows/bootstrap.yml
create mode 100644 .github/workflows/ci.yml
delete mode 100644 .magicsettings-source.part-00
delete mode 100644 .magicsettings-source.part-01
delete mode 100644 .magicsettings-source.part-02
delete mode 100644 .magicsettings-source.part-s00
delete mode 100644 .magicsettings-source.part-s01
delete mode 100644 .magicsettings-source.part-s02
delete mode 100644 .magicsettings-source.part-s03
delete mode 100644 .magicsettings-source.part-s04
delete mode 100644 .magicsettings-source.part-s05
delete mode 100644 .magicsettings-source.part-s06
delete mode 100644 .magicsettings-source.part-s07
delete mode 100644 .magicsettings-source.part-s08
delete mode 100644 .magicsettings-source.part-s09
delete mode 100644 .magicsettings-source.part-s10
delete mode 100644 .magicsettings-source.part-s11
delete mode 100644 .magicsettings-source.part-s12
create mode 100644 MagicSettings/Directory.Build.props
create mode 100644 MagicSettings/MagicSettings.Server/Source.01.cs
create mode 100644 MagicSettings/MagicSettings.Server/Source.02.cs
create mode 100644 MagicSettings/MagicSettings.Share/ConfigurationContracts.cs
create mode 100644 MagicSettings/MagicSettings.Share/ControlPlaneContracts.cs
create mode 100644 MagicSettings/MagicSettings.Share/ProofCodec.cs
create mode 100644 MagicSettings/MagicSettings.Share/SecretProof.cs
create mode 100644 MagicSettings/MagicSettings.Share/SecurityContracts.cs
create mode 100644 MagicSettings/MagicSettings.Share/SyncProof.cs
create mode 100644 MagicSettings/MagicSettings.Tests/Source.01.cs
create mode 100644 MagicSettings/MagicSettings.Tests/Source.02.cs
delete mode 100644 MagicSettings/MagicSettings.Tests/UnitTest1.cs
create mode 100644 MagicSettings/MagicSettings/Source.01.cs
create mode 100644 MagicSettings/MagicSettings/Source.02.cs
create mode 100644 MagicSettings/MagicSettings/Source.03.cs
create mode 100644 MagicSettings/MagicSettings/Source.04.cs
create mode 100644 MagicSettings/MagicSettings/Source.05.cs
create mode 100644 MagicSettings/MagicSettings/Source.06.cs
create mode 100644 MagicSettings/MagicSettings/Source.07.cs
create mode 100644 wiki/README.md
create mode 100644 wiki/configuration.md
create mode 100644 wiki/control-plane.md
create mode 100644 wiki/getting-started.md
create mode 100644 wiki/migrations.md
create mode 100644 wiki/secrets-and-security.md
create mode 100644 wiki/testing.md
diff --git a/.github/workflows/bootstrap.yml b/.github/workflows/bootstrap.yml
deleted file mode 100644
index 3657bd4..0000000
--- a/.github/workflows/bootstrap.yml
+++ /dev/null
@@ -1,59 +0,0 @@
-name: Bootstrap MagicSettings implementation
-
-on:
- push:
- branches:
- - agent/build-magicsettings-runtime
- pull_request:
- branches:
- - main
-
-permissions:
- contents: write
-
-jobs:
- materialize:
- runs-on: ubuntu-latest
- steps:
- - name: Check out source branch
- uses: actions/checkout@v4
- with:
- ref: ${{ github.head_ref || github.ref_name }}
-
- - name: Rebuild source tree
- shell: bash
- run: |
- set -euo pipefail
- cat .magicsettings-source.part-* > /tmp/magicsettings-source.tar.gz.b64
- base64 --decode /tmp/magicsettings-source.tar.gz.b64 > /tmp/magicsettings-source.tar.gz
- rm -rf MagicSettings wiki README.md .gitignore .github
- tar -xzf /tmp/magicsettings-source.tar.gz -C .
- rm -f .magicsettings-source.part-*
- rm -f .github/workflows/bootstrap.yml
-
- - name: Set up .NET 10
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: 10.0.x
-
- - name: Restore
- working-directory: MagicSettings
- run: dotnet restore MagicSettings.sln
-
- - name: Build
- working-directory: MagicSettings
- run: dotnet build MagicSettings.sln --configuration Release --no-restore
-
- - name: Test
- working-directory: MagicSettings
- run: dotnet test MagicSettings.sln --configuration Release --no-build --logger "console;verbosity=normal"
-
- - name: Commit implementation
- shell: bash
- run: |
- set -euo pipefail
- git config user.name "github-actions[bot]"
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- git add -A
- git commit -m "Build MagicSettings runtime"
- git push origin HEAD:agent/build-magicsettings-runtime
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..32b0eeb
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,25 @@
+name: CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+permissions:
+ contents: read
+
+jobs:
+ build-and-test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 10.0.x
+ - run: dotnet restore MagicSettings.sln
+ working-directory: MagicSettings
+ - run: dotnet build MagicSettings.sln --configuration Release --no-restore
+ working-directory: MagicSettings
+ - run: dotnet test MagicSettings.sln --configuration Release --no-build --logger "console;verbosity=normal"
+ working-directory: MagicSettings
diff --git a/.gitignore b/.gitignore
index e2f136b..6840df3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,22 +1,17 @@
-## Build output
[Bb]in/
[Oo]bj/
artifacts/
-*.user
-*.suo
+TestResults/
+coverage/
.idea/
.vs/
-
-## Runtime state and secrets
-**/.magic-ai/
-**/state/
+*.user
+*.suo
*.pfx
*.pem
*.key
*.password
-appsettings.Local.json
.env
-
-## Test output
-TestResults/
-coverage/
+**/.magicsettings.identity.json
+**/.magicsettings.identity.json.*
+appsettings.Local.json
diff --git a/.magicsettings-source.part-00 b/.magicsettings-source.part-00
deleted file mode 100644
index 2285b40..0000000
--- a/.magicsettings-source.part-00
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git a/.magicsettings-source.part-01 b/.magicsettings-source.part-01
deleted file mode 100644
index 19f066d..0000000
--- a/.magicsettings-source.part-01
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git a/.magicsettings-source.part-02 b/.magicsettings-source.part-02
deleted file mode 100644
index f038300..0000000
--- a/.magicsettings-source.part-02
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s00 b/.magicsettings-source.part-s00
deleted file mode 100644
index e5b1f30..0000000
--- a/.magicsettings-source.part-s00
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s01 b/.magicsettings-source.part-s01
deleted file mode 100644
index 0e1eed5..0000000
--- a/.magicsettings-source.part-s01
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s02 b/.magicsettings-source.part-s02
deleted file mode 100644
index 17209e5..0000000
--- a/.magicsettings-source.part-s02
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s03 b/.magicsettings-source.part-s03
deleted file mode 100644
index 89b2969..0000000
--- a/.magicsettings-source.part-s03
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s04 b/.magicsettings-source.part-s04
deleted file mode 100644
index 5281a9b..0000000
--- a/.magicsettings-source.part-s04
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s05 b/.magicsettings-source.part-s05
deleted file mode 100644
index ebabf68..0000000
--- a/.magicsettings-source.part-s05
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s06 b/.magicsettings-source.part-s06
deleted file mode 100644
index a7839d8..0000000
--- a/.magicsettings-source.part-s06
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s07 b/.magicsettings-source.part-s07
deleted file mode 100644
index 150c834..0000000
--- a/.magicsettings-source.part-s07
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s08 b/.magicsettings-source.part-s08
deleted file mode 100644
index 8e266bb..0000000
--- a/.magicsettings-source.part-s08
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s09 b/.magicsettings-source.part-s09
deleted file mode 100644
index ac154d7..0000000
--- a/.magicsettings-source.part-s09
+++ /dev/null
@@ -1 +0,0 @@
-E0c38BSqKmTByH2MEz0KXoK8Kigu0bQ9OJOfgQ8CBGxLNtcr7qT2twyUWlmcnb4AtgSkT2IagXdhxA35Pmnw+8qSQUpy9HpJVcZaEk8uzA+EwRuE5yO1fY+58YLWS8s0OohN3OujNeczBHRGqi3/Jvqu+WVfzP9V+R/d+ht1/n/ry67+b2N5Hv99P9f3QZl/WiEflNTdU2Yi1/yq0kGYCUU06IQ6RGTgF2cG8tuBTWfabjoCIfG9yVEekOiG3yOgQGQqzCLaNsAlUE5jUdt2v5OSp2GuxuwZUjeITQXkE+BWyfz93jSbvQfm9v2LvWdH+yeHp6dHL5+dnB3vnf78HljY93ujEcOxh0/EYX+AZj/BYzx0WeT3jLsis6v0ZEl0mE8kmIdRApVMmEBXiLPYbDx9UeQcg92GAEcxUppQAC0scXydjPMeRBZlEIzH3TJjIPKRKllLYQC2XWOiMuVBvuDgE7AEYplhDpacXKxxDaTo6cmYgJR/0sgjr8eFlEoaSE/s5Mt4TDw9cwyC7dCI8FD+oj8hjHROCS+OeKUG5chsstdKGEbOfjIZCXkFhxeduznZAesTUIJ5/wSLvZaP9yHnAXrEizrTPF+Upz+k35JZ7yFyLAfzO6rKyKYuoQZwfDJxmD8pALxHSvM9GfUwyMFxA4iyHLQSDAaG4RMMKga5Z5JhjwMfSmDmFbg8bZgDmTMa9d2ch1Uw6xAsujeWbekQGig9FVsddTTIvkBPxKjwlGo8SOUq4AI/KH5E6gDZx8REnrCQJCmpTV/wXhqyASul1jiu95xsLEcQS+nxYFGdfNITE4Kcl/Js4gjK4aWaAZPz/HMySHz+O27Ed/uNuvivtXU3/kv8szw//+/jsvN/KcdyhmkvOnbh5kULuP9ctA+Bw5e/Hr1+9fLF4ctTOiQPXp2+PDy17+MheXIs7u+/en1oP1szjtqnEaVpNhr8vsOxS0wUUFx+r49+cdh4uRGbHIBxkFXRbMMBBsfLmXixy4xPtFsGa9LqaKZCOQ6qI9T0WzTnQBy5Zh2P7LekBiZEdw/HwePGjBNT7IvllwcUMhmKQxAUeDmfrQx1SxFxMn+8mCzdDDHQ75/Dl97TsfEevJXfsy4FbcAmZdYTsNhPPlgLKEesdhTIWV9GYDOUzZI8QDTW8aJ9GFDyaZKWC3mu8w4qSqIeq3LQ68eeaWo7QeeiqY3VFTL3tRNAj8iZMCoa0O6P3q53fjH9L8SC3OU36uS/pfVC/t+V5bn/x71c5FVneAo8sH8b7JTXvN5PLocyEFTKc1qOk96zY8lCKTod5SSmdDG+K0X3LOR3tX8t+949BYgLctjreBC6ol4vNx12JV1ABzDJGRKgluCrc3GeEVPHzLDjEggFJYGS0bXKfGcmd7QLSvxLox2aHkJRbRNFoDKO85hw2JuCdEy16lXTNW0/QARctBLgMYR2FThVVMRwa4s9O1twPIhfrd55iAGlw6jfIk/QlooqFo9Bbc4K+NYDI8vWHkpPxQnF0e5T8syv1ggo0uIMnbqyF4KkDyaD53AowVtHGpVYVmon82wRCOQDK3mYtbYTCGnMaQWLsT9HKPYBIQCjH7nte6FHAYDi8zGbPhRYs+nSyPHKIN4ptHadW4Vj7LLYDqkU2wFAtTkYGgBkaCnEGYoc0o5ACasHcUQZBeTyw3NP7jhvpjx5sp/EcfA28gX6VQb4/Rsef//xl/L/LJn0u/hGnfy3WcB/WV+f43/czwX+n1UBv2RrQ7AoUI4p4yHiI421BV86LnaU/R4RKjpapaTO9e5VKn1zjA+mF5A+xHTOd2C3FBSD+GeS02l5OQF9GskOpp8Ntpc84XLNCUj6LSjw0VDKLaZsggqsyZDpJfQKCSYCgXzKXXTr4GhoeKUXJR0yjTuNSrQA1OPkBQxxgsAbA9CTgTFUYUlbqBPUnhIAbYuDF1wTTizFgbT2AEUz7r1ChPqcz0scYCxlv3xMx8SbofhbQVYr5wa5MggH9Jg7
\ No newline at end of file
diff --git a/.magicsettings-source.part-s10 b/.magicsettings-source.part-s10
deleted file mode 100644
index 079ede2..0000000
--- a/.magicsettings-source.part-s10
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s11 b/.magicsettings-source.part-s11
deleted file mode 100644
index 451d12a..0000000
--- a/.magicsettings-source.part-s11
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git a/.magicsettings-source.part-s12 b/.magicsettings-source.part-s12
deleted file mode 100644
index 62d4865..0000000
--- a/.magicsettings-source.part-s12
+++ /dev/null
@@ -1 +0,0 @@
-cxz1VJHu4PkR6KJABZdrZmJx0Z4MiUUYF58gvql6HiwuXovxLpSSjG3xCSrfENyw7JmDk/u+tHHv7WRqrPgs0UW46iMz4SQEXJoZ2JDdFkQlLwwmQgSIDRY8RZhXwyFWChURO53KfHs6c5kZIkl4WcOeFAuEMIIj+Z4Bv+w0wJAM07wDGb3jz+/adu5oTGNZyMFbkl+Sk82W5+H15aWR6VBLcvHWQZnj66V4h/x+dSreQh5dzmIMMbRcgT+vrpkZF4uVpsc91rlhsWBZItqytLByFEpzw3rSo8ogQDdHqp0Flcs1zXmqq/UlOuWnbpbe54JMDnM4JUaYuJ1/Byvh0jzSZH7Nr/k1v+bX/Jpf82t+za/5Nb/m1/yaX/Nrfs2v6a7/HyYscTAAOAQA
\ No newline at end of file
diff --git a/MagicSettings/Directory.Build.props b/MagicSettings/Directory.Build.props
new file mode 100644
index 0000000..4bd9c1a
--- /dev/null
+++ b/MagicSettings/Directory.Build.props
@@ -0,0 +1,12 @@
+
+
+ net10.0
+ latest
+ enable
+ enable
+ true
+ true
+ $(NoWarn);1591
+ true
+
+
diff --git a/MagicSettings/MagicSettings.Server/MagicSettings.Server.csproj b/MagicSettings/MagicSettings.Server/MagicSettings.Server.csproj
index 237d661..a46af5e 100644
--- a/MagicSettings/MagicSettings.Server/MagicSettings.Server.csproj
+++ b/MagicSettings/MagicSettings.Server/MagicSettings.Server.csproj
@@ -1,9 +1,10 @@
-
-
-
- net10.0
- enable
- enable
-
-
+
+
+ MagicSettings.Server
+ Storage-agnostic server helpers for MagicSettings enrollment, synchronization, authorization, and proof verification.
+
+
+
+
+
diff --git a/MagicSettings/MagicSettings.Server/Source.01.cs b/MagicSettings/MagicSettings.Server/Source.01.cs
new file mode 100644
index 0000000..e896af6
--- /dev/null
+++ b/MagicSettings/MagicSettings.Server/Source.01.cs
@@ -0,0 +1,436 @@
+// Consolidated source file. See repository history and wiki for subsystem boundaries.
+using System.Security.Cryptography;
+using MagicSettings.Share;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Concurrent;
+using System.Text;
+
+namespace MagicSettings.Server
+{
+public static class MagicAspNetProofVerificationExtensions
+{
+ public static async ValueTask VerifyHttpRequestAsync(
+ this MagicNodeProofVerifier verifier,
+ HttpRequest request,
+ string expectedAudience,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(verifier);
+ ArgumentNullException.ThrowIfNull(request);
+ ArgumentException.ThrowIfNullOrWhiteSpace(expectedAudience);
+
+ if (!request.Headers.TryGetValue("Authorization", out var authorization))
+ {
+ return MagicProofVerificationResult.Invalid("The MagicNode authorization header is missing.");
+ }
+
+ var header = authorization.ToString();
+ const string prefix = "MagicNode ";
+ if (!header.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ {
+ return MagicProofVerificationResult.Invalid("The authorization scheme is not MagicNode.");
+ }
+
+ MagicAuthenticationProof proof;
+ try
+ {
+ proof = MagicNodeProofCodec.Decode(header[prefix.Length..].Trim());
+ }
+ catch (Exception exception) when (exception is FormatException or System.Text.Json.JsonException)
+ {
+ return MagicProofVerificationResult.Invalid("The MagicNode authorization proof is malformed.");
+ }
+
+ request.EnableBuffering();
+ string bodyHash;
+ if (request.ContentLength is null or 0)
+ {
+ bodyHash = Convert.ToHexString(SHA256.HashData(Array.Empty())).ToLowerInvariant();
+ }
+ else
+ {
+ await using var buffer = new MemoryStream();
+ await request.Body.CopyToAsync(buffer, cancellationToken);
+ request.Body.Position = 0;
+ bodyHash = Convert.ToHexString(SHA256.HashData(buffer.ToArray())).ToLowerInvariant();
+ }
+
+ var uri = new Uri($"{request.Scheme}://{request.Host}{request.PathBase}{request.Path}{request.QueryString}");
+ return await verifier.VerifyAsync(
+ new(proof, expectedAudience, request.Method, uri, bodyHash, DateTimeOffset.UtcNow),
+ cancellationToken);
+ }
+}
+}
+namespace MagicSettings.Server
+{
+///
+/// Storage-agnostic credential lifecycle helpers for control-plane implementations.
+///
+public sealed class MagicCredentialAdministrationService
+{
+ private readonly IMagicCredentialRegistry _credentials;
+
+ public MagicCredentialAdministrationService(IMagicCredentialRegistry credentials) => _credentials = credentials;
+
+ public async ValueTask SetStatusAsync(
+ Guid nodeId,
+ Guid credentialId,
+ MagicCredentialStatus status,
+ CancellationToken cancellationToken = default)
+ {
+ var credential = await _credentials.FindAsync(nodeId, credentialId, cancellationToken);
+ if (credential is null)
+ {
+ return false;
+ }
+
+ await _credentials.UpsertAsync(credential with
+ {
+ Status = status,
+ UpdatedUtc = DateTimeOffset.UtcNow
+ }, cancellationToken);
+ return true;
+ }
+
+ public ValueTask ApproveAsync(Guid nodeId, Guid credentialId, CancellationToken cancellationToken = default)
+ => SetStatusAsync(nodeId, credentialId, MagicCredentialStatus.Approved, cancellationToken);
+
+ public ValueTask RevokeAsync(Guid nodeId, Guid credentialId, CancellationToken cancellationToken = default)
+ => SetStatusAsync(nodeId, credentialId, MagicCredentialStatus.Revoked, cancellationToken);
+}
+}
+namespace MagicSettings.Server
+{
+public sealed class MagicCredentialRotationService
+{
+ private readonly IMagicCredentialRegistry _credentials;
+
+ public MagicCredentialRotationService(IMagicCredentialRegistry credentials) => _credentials = credentials;
+
+ public async ValueTask ApplyAsync(
+ MagicIdentityContinuityProof continuity,
+ bool autoApproveNewCredential,
+ CancellationToken cancellationToken = default)
+ {
+ if (!MagicNodeProofVerifier.VerifyContinuity(continuity))
+ {
+ return MagicProofVerificationResult.Invalid("The identity continuity proof is invalid.");
+ }
+
+ var previous = await _credentials.FindAsync(
+ continuity.PreviousIdentity.NodeId,
+ continuity.PreviousIdentity.CredentialId,
+ cancellationToken);
+ if (previous is null)
+ {
+ return MagicProofVerificationResult.Invalid("The previous credential is unknown.");
+ }
+
+ if (previous.Status is not (MagicCredentialStatus.Approved or MagicCredentialStatus.Retiring))
+ {
+ return MagicProofVerificationResult.Invalid("The previous credential is not authorized to rotate.");
+ }
+
+ if (!string.Equals(previous.PublicKey, continuity.PreviousIdentity.PublicKey, StringComparison.Ordinal))
+ {
+ return MagicProofVerificationResult.Invalid("The continuity proof does not match the registered previous key.");
+ }
+
+ await _credentials.UpsertAsync(previous with
+ {
+ Status = MagicCredentialStatus.Retiring,
+ UpdatedUtc = DateTimeOffset.UtcNow
+ }, cancellationToken);
+
+ await _credentials.UpsertAsync(new(
+ continuity.NewIdentity.NodeId,
+ continuity.NewIdentity.CredentialId,
+ continuity.NewIdentity.PublicKey,
+ autoApproveNewCredential ? MagicCredentialStatus.Approved : MagicCredentialStatus.Pending,
+ DateTimeOffset.UtcNow), cancellationToken);
+
+ return MagicProofVerificationResult.Valid;
+ }
+}
+}
+namespace MagicSettings.Server
+{
+public sealed class InMemoryMagicCredentialRegistry : IMagicCredentialRegistry
+{
+ private readonly ConcurrentDictionary<(Guid NodeId, Guid CredentialId), MagicRegisteredCredential> _credentials = new();
+
+ public ValueTask FindAsync(Guid nodeId, Guid credentialId, CancellationToken cancellationToken = default)
+ => ValueTask.FromResult(_credentials.TryGetValue((nodeId, credentialId), out var credential) ? credential : null);
+
+ public ValueTask UpsertAsync(MagicRegisteredCredential credential, CancellationToken cancellationToken = default)
+ {
+ _credentials[(credential.NodeId, credential.CredentialId)] = credential;
+ return ValueTask.CompletedTask;
+ }
+}
+
+public sealed class InMemoryMagicReplayCache : IMagicReplayCache
+{
+ private readonly ConcurrentDictionary _nonces = new(StringComparer.Ordinal);
+
+ public ValueTask TryUseAsync(Guid credentialId, string nonce, DateTimeOffset expiresUtc, CancellationToken cancellationToken = default)
+ {
+ var now = DateTimeOffset.UtcNow;
+ foreach (var pair in _nonces)
+ {
+ if (pair.Value <= now)
+ {
+ _nonces.TryRemove(pair.Key, out _);
+ }
+ }
+
+ return ValueTask.FromResult(_nonces.TryAdd($"{credentialId:D}:{nonce}", expiresUtc));
+ }
+}
+
+public sealed class InMemoryMagicNodeRemoteRecordStore : IMagicNodeRemoteRecordStore
+{
+ private readonly ConcurrentDictionary<(Guid NodeId, string ApplicationId), MagicNodeRemoteRecord> _records = new();
+
+ public ValueTask GetAsync(Guid nodeId, string applicationId, CancellationToken cancellationToken = default)
+ => ValueTask.FromResult(_records.TryGetValue((nodeId, applicationId), out var record) ? record : null);
+
+ public ValueTask SaveAsync(MagicNodeRemoteRecord record, CancellationToken cancellationToken = default)
+ {
+ _records[(record.NodeId, record.ApplicationId)] = record;
+ return ValueTask.CompletedTask;
+ }
+}
+}
+namespace MagicSettings.Server
+{
+public sealed class MagicNodeProofVerifier
+{
+ private readonly IMagicCredentialRegistry _credentials;
+ private readonly IMagicReplayCache _replayCache;
+ private readonly TimeSpan _allowedClockSkew;
+
+ public MagicNodeProofVerifier(IMagicCredentialRegistry credentials, IMagicReplayCache replayCache, TimeSpan? allowedClockSkew = null)
+ {
+ _credentials = credentials;
+ _replayCache = replayCache;
+ _allowedClockSkew = allowedClockSkew ?? TimeSpan.FromSeconds(30);
+ }
+
+ public async ValueTask VerifyAsync(MagicProofVerificationRequest request, CancellationToken cancellationToken = default)
+ {
+ var structural = ValidateRequest(request);
+ if (!structural.IsValid)
+ {
+ return structural;
+ }
+
+ var proof = request.Proof;
+ var credential = await _credentials.FindAsync(proof.NodeId, proof.CredentialId, cancellationToken);
+ if (credential is null)
+ {
+ return MagicProofVerificationResult.Invalid("The credential is unknown.");
+ }
+
+ if (credential.Status is not (MagicCredentialStatus.Approved or MagicCredentialStatus.Retiring))
+ {
+ return MagicProofVerificationResult.Invalid($"The credential is {credential.Status}.");
+ }
+
+ return await VerifySignatureAndReplayAsync(credential.PublicKey, request, cancellationToken);
+ }
+
+ public async ValueTask VerifyEnrollmentAsync(
+ MagicNodeIdentityDescriptor identity,
+ MagicProofVerificationRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(identity);
+ var structural = ValidateRequest(request);
+ if (!structural.IsValid)
+ {
+ return structural;
+ }
+
+ if (request.Proof.NodeId != identity.NodeId || request.Proof.CredentialId != identity.CredentialId)
+ {
+ return MagicProofVerificationResult.Invalid("The proof does not match the supplied node identity.");
+ }
+
+ if (identity.CredentialKind != MagicCredentialKind.EcdsaP256
+ || !string.Equals(identity.SignatureAlgorithm, "ECDSA_P256_SHA256_P1363", StringComparison.Ordinal))
+ {
+ return MagicProofVerificationResult.Invalid("The supplied credential algorithm is unsupported.");
+ }
+
+ string fingerprint;
+ try
+ {
+ fingerprint = Convert.ToHexString(SHA256.HashData(Convert.FromBase64String(identity.PublicKey))).ToLowerInvariant();
+ }
+ catch (FormatException)
+ {
+ return MagicProofVerificationResult.Invalid("The supplied public key is malformed.");
+ }
+
+ if (!CryptographicOperations.FixedTimeEquals(
+ Encoding.ASCII.GetBytes(fingerprint),
+ Encoding.ASCII.GetBytes(identity.Fingerprint.ToLowerInvariant())))
+ {
+ return MagicProofVerificationResult.Invalid("The supplied public-key fingerprint is invalid.");
+ }
+
+ return await VerifySignatureAndReplayAsync(identity.PublicKey, request, cancellationToken);
+ }
+
+ public static bool VerifyContinuity(MagicIdentityContinuityProof proof)
+ {
+ var canonical = string.Join("\n",
+ "MAGICSETTINGS-IDENTITY-CONTINUITY-V1",
+ proof.PreviousIdentity.NodeId.ToString("D"),
+ proof.PreviousIdentity.CredentialId.ToString("D"),
+ proof.NewIdentity.CredentialId.ToString("D"),
+ proof.NewIdentity.PublicKey,
+ proof.IssuedUtc.ToUnixTimeMilliseconds().ToString(System.Globalization.CultureInfo.InvariantCulture),
+ proof.Nonce);
+ return VerifySignature(proof.PreviousIdentity.PublicKey, canonical, proof.Signature)
+ && proof.PreviousIdentity.NodeId == proof.NewIdentity.NodeId;
+ }
+
+ private MagicProofVerificationResult ValidateRequest(MagicProofVerificationRequest request)
+ {
+ ArgumentNullException.ThrowIfNull(request);
+ var proof = request.Proof;
+ if (!string.Equals(proof.Version, "MAGICSETTINGS-PROOF-V1", StringComparison.Ordinal))
+ return MagicProofVerificationResult.Invalid("Unsupported proof version.");
+ if (!string.Equals(proof.Audience, request.ExpectedAudience, StringComparison.Ordinal))
+ return MagicProofVerificationResult.Invalid("The proof audience does not match this API.");
+ if (!string.Equals(proof.Method, request.Method, StringComparison.OrdinalIgnoreCase))
+ return MagicProofVerificationResult.Invalid("The HTTP method does not match the signed proof.");
+ if (!string.Equals(proof.Target, NormalizeTarget(request.Uri), StringComparison.Ordinal))
+ return MagicProofVerificationResult.Invalid("The request target does not match the signed proof.");
+ if (!string.Equals(proof.BodySha256, request.BodySha256, StringComparison.OrdinalIgnoreCase))
+ return MagicProofVerificationResult.Invalid("The request body hash does not match the signed proof.");
+ if (proof.IssuedUtc - _allowedClockSkew > request.NowUtc)
+ return MagicProofVerificationResult.Invalid("The proof was issued in the future.");
+ if (proof.ExpiresUtc + _allowedClockSkew < request.NowUtc)
+ return MagicProofVerificationResult.Invalid("The proof has expired.");
+ if (proof.ExpiresUtc <= proof.IssuedUtc || proof.ExpiresUtc - proof.IssuedUtc > TimeSpan.FromMinutes(5))
+ return MagicProofVerificationResult.Invalid("The proof lifetime is invalid.");
+ if (string.IsNullOrWhiteSpace(proof.Nonce))
+ return MagicProofVerificationResult.Invalid("The proof nonce is missing.");
+ return MagicProofVerificationResult.Valid;
+ }
+
+ private async ValueTask VerifySignatureAndReplayAsync(
+ string publicKey,
+ MagicProofVerificationRequest request,
+ CancellationToken cancellationToken)
+ {
+ var proof = request.Proof;
+ if (!VerifySignature(publicKey, Canonicalize(proof), proof.Signature))
+ {
+ return MagicProofVerificationResult.Invalid("The proof signature is invalid.");
+ }
+
+ if (!await _replayCache.TryUseAsync(proof.CredentialId, proof.Nonce, proof.ExpiresUtc, cancellationToken))
+ {
+ return MagicProofVerificationResult.Invalid("The proof nonce has already been used.");
+ }
+
+ return MagicProofVerificationResult.Valid;
+ }
+
+ private static bool VerifySignature(string publicKey, string canonical, string signature)
+ {
+ try
+ {
+ using var key = ECDsa.Create();
+ key.ImportSubjectPublicKeyInfo(Convert.FromBase64String(publicKey), out _);
+ return key.VerifyData(
+ Encoding.UTF8.GetBytes(canonical),
+ Convert.FromBase64String(signature),
+ HashAlgorithmName.SHA256,
+ DSASignatureFormat.IeeeP1363FixedFieldConcatenation);
+ }
+ catch (CryptographicException)
+ {
+ return false;
+ }
+ catch (FormatException)
+ {
+ return false;
+ }
+ }
+
+ private static string Canonicalize(MagicAuthenticationProof proof)
+ => string.Join("\n",
+ proof.Version,
+ proof.NodeId.ToString("D"),
+ proof.CredentialId.ToString("D"),
+ proof.Audience,
+ proof.Method.ToUpperInvariant(),
+ proof.Target,
+ proof.BodySha256.ToLowerInvariant(),
+ proof.IssuedUtc.ToUnixTimeMilliseconds().ToString(System.Globalization.CultureInfo.InvariantCulture),
+ proof.ExpiresUtc.ToUnixTimeMilliseconds().ToString(System.Globalization.CultureInfo.InvariantCulture),
+ proof.Nonce);
+
+ private static string NormalizeTarget(Uri uri)
+ => uri.IsAbsoluteUri
+ ? uri.GetComponents(UriComponents.SchemeAndServer | UriComponents.PathAndQuery, UriFormat.UriEscaped)
+ : uri.OriginalString;
+}
+}
+namespace MagicSettings.Server
+{
+public interface IMagicSecretResolver
+{
+ ValueTask ResolveAsync(
+ Guid nodeId,
+ string name,
+ CancellationToken cancellationToken = default);
+}
+
+public sealed class MagicSecretService
+{
+ private readonly MagicNodeProofVerifier _proofVerifier;
+ private readonly IMagicSecretResolver _resolver;
+
+ public MagicSecretService(MagicNodeProofVerifier proofVerifier, IMagicSecretResolver resolver)
+ {
+ _proofVerifier = proofVerifier;
+ _resolver = resolver;
+ }
+
+ public async ValueTask ResolveAsync(
+ MagicSecretRequest request,
+ string authorityAudience,
+ Uri requestUri,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(request);
+ if (request.NodeId != request.Proof.NodeId || request.CredentialId != request.Proof.CredentialId)
+ {
+ throw new UnauthorizedAccessException("The secret request identity does not match its proof.");
+ }
+
+ var verification = await _proofVerifier.VerifyAsync(
+ new(
+ request.Proof,
+ authorityAudience,
+ "POST",
+ requestUri,
+ MagicSecretProof.ComputeBodySha256(request.Name),
+ DateTimeOffset.UtcNow),
+ cancellationToken);
+ if (!verification.IsValid)
+ {
+ throw new UnauthorizedAccessException(verification.Error);
+ }
+
+ return await _resolver.ResolveAsync(request.NodeId, request.Name, cancellationToken);
+ }
+}
+}
diff --git a/MagicSettings/MagicSettings.Server/Source.02.cs b/MagicSettings/MagicSettings.Server/Source.02.cs
new file mode 100644
index 0000000..a937d12
--- /dev/null
+++ b/MagicSettings/MagicSettings.Server/Source.02.cs
@@ -0,0 +1,164 @@
+// Consolidated source file. See repository history and wiki for subsystem boundaries.
+using System.Security.Cryptography;
+using MagicSettings.Share;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Concurrent;
+using System.Text;
+
+namespace MagicSettings.Server
+{
+public sealed record MagicRegisteredCredential(
+ Guid NodeId,
+ Guid CredentialId,
+ string PublicKey,
+ MagicCredentialStatus Status,
+ DateTimeOffset UpdatedUtc);
+
+public interface IMagicCredentialRegistry
+{
+ ValueTask FindAsync(Guid nodeId, Guid credentialId, CancellationToken cancellationToken = default);
+ ValueTask UpsertAsync(MagicRegisteredCredential credential, CancellationToken cancellationToken = default);
+}
+
+public interface IMagicReplayCache
+{
+ ValueTask TryUseAsync(Guid credentialId, string nonce, DateTimeOffset expiresUtc, CancellationToken cancellationToken = default);
+}
+
+public sealed record MagicNodeRemoteRecord(
+ Guid NodeId,
+ string ApplicationId,
+ int SchemaVersion,
+ string SchemaFingerprint,
+ long Revision,
+ MagicRemoteSnapshot Snapshot,
+ IReadOnlyList PendingReviewItems,
+ DateTimeOffset LastContactUtc);
+
+public interface IMagicNodeRemoteRecordStore
+{
+ ValueTask GetAsync(Guid nodeId, string applicationId, CancellationToken cancellationToken = default);
+ ValueTask SaveAsync(MagicNodeRemoteRecord record, CancellationToken cancellationToken = default);
+}
+}
+namespace MagicSettings.Server
+{
+public sealed class MagicSettingsSyncService
+{
+ private readonly IMagicCredentialRegistry _credentials;
+ private readonly IMagicNodeRemoteRecordStore _records;
+ private readonly MagicNodeProofVerifier _proofVerifier;
+ private readonly MagicCredentialRotationService _rotationService;
+ private readonly bool _autoApproveRotatedCredentials;
+
+ public MagicSettingsSyncService(
+ IMagicCredentialRegistry credentials,
+ IMagicNodeRemoteRecordStore records,
+ MagicNodeProofVerifier proofVerifier,
+ bool autoApproveRotatedCredentials = false)
+ {
+ _credentials = credentials;
+ _records = records;
+ _proofVerifier = proofVerifier;
+ _rotationService = new MagicCredentialRotationService(credentials);
+ _autoApproveRotatedCredentials = autoApproveRotatedCredentials;
+ }
+
+ public async ValueTask SynchronizeAsync(
+ MagicSettingsSyncRequest request,
+ string authorityAudience,
+ Uri requestUri,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(request);
+ var signedPayloadHash = MagicSettingsSyncProof.ComputeBodySha256(
+ request.Identity,
+ request.Manifest,
+ request.LastRemoteRevision,
+ request.MigrationReport,
+ request.IdentityContinuityProof);
+ var verificationRequest = new MagicProofVerificationRequest(
+ request.Proof,
+ authorityAudience,
+ "POST",
+ requestUri,
+ signedPayloadHash,
+ DateTimeOffset.UtcNow);
+
+ var registered = await _credentials.FindAsync(request.Identity.NodeId, request.Identity.CredentialId, cancellationToken);
+ if (registered is null && request.IdentityContinuityProof is { } continuity)
+ {
+ if (continuity.NewIdentity.NodeId != request.Identity.NodeId
+ || continuity.NewIdentity.CredentialId != request.Identity.CredentialId
+ || !string.Equals(continuity.NewIdentity.PublicKey, request.Identity.PublicKey, StringComparison.Ordinal))
+ {
+ return new(MagicControlPlaneState.Faulted, MagicRemoteSnapshot.Empty, "The rotation proof does not describe the supplied current identity.");
+ }
+
+ var rotation = await _rotationService.ApplyAsync(continuity, _autoApproveRotatedCredentials, cancellationToken);
+ if (!rotation.IsValid)
+ {
+ return new(MagicControlPlaneState.Faulted, MagicRemoteSnapshot.Empty, rotation.Error);
+ }
+
+ registered = await _credentials.FindAsync(request.Identity.NodeId, request.Identity.CredentialId, cancellationToken);
+ }
+
+ if (registered is null)
+ {
+ var enrollmentVerification = await _proofVerifier.VerifyEnrollmentAsync(request.Identity, verificationRequest, cancellationToken);
+ if (!enrollmentVerification.IsValid)
+ {
+ return new(MagicControlPlaneState.Faulted, MagicRemoteSnapshot.Empty, enrollmentVerification.Error);
+ }
+
+ await _credentials.UpsertAsync(
+ new(request.Identity.NodeId, request.Identity.CredentialId, request.Identity.PublicKey, MagicCredentialStatus.Pending, DateTimeOffset.UtcNow),
+ cancellationToken);
+ return new(MagicControlPlaneState.PendingApproval, MagicRemoteSnapshot.Empty, "Node credential is pending approval.");
+ }
+
+ if (registered.Status == MagicCredentialStatus.Pending)
+ {
+ return new(MagicControlPlaneState.PendingApproval, MagicRemoteSnapshot.Empty, "Node credential is pending approval.");
+ }
+
+ if (!string.Equals(registered.PublicKey, request.Identity.PublicKey, StringComparison.Ordinal))
+ {
+ return new(MagicControlPlaneState.Faulted, MagicRemoteSnapshot.Empty, "The supplied identity does not match the registered credential.");
+ }
+
+ var verification = await _proofVerifier.VerifyAsync(verificationRequest, cancellationToken);
+ if (!verification.IsValid)
+ {
+ return new(MagicControlPlaneState.Faulted, MagicRemoteSnapshot.Empty, verification.Error);
+ }
+
+ var existing = await _records.GetAsync(request.Identity.NodeId, request.Manifest.ApplicationId, cancellationToken);
+ var reviewItems = existing?.PendingReviewItems.ToList() ?? new List();
+ if (request.MigrationReport is { } migrationReport)
+ {
+ foreach (var item in migrationReport.ReviewItems)
+ {
+ if (!reviewItems.Contains(item))
+ {
+ reviewItems.Add(item);
+ }
+ }
+ }
+
+ var snapshot = existing?.Snapshot ?? MagicRemoteSnapshot.Empty;
+ var updated = new MagicNodeRemoteRecord(
+ request.Identity.NodeId,
+ request.Manifest.ApplicationId,
+ request.Manifest.SchemaVersion,
+ request.Manifest.SchemaFingerprint,
+ existing?.Revision ?? 0,
+ snapshot,
+ reviewItems,
+ DateTimeOffset.UtcNow);
+ await _records.SaveAsync(updated, cancellationToken);
+ return new(MagicControlPlaneState.Active, snapshot);
+ }
+}
+}
diff --git a/MagicSettings/MagicSettings.Share/ConfigurationContracts.cs b/MagicSettings/MagicSettings.Share/ConfigurationContracts.cs
new file mode 100644
index 0000000..996d27c
--- /dev/null
+++ b/MagicSettings/MagicSettings.Share/ConfigurationContracts.cs
@@ -0,0 +1,73 @@
+using System.Collections.ObjectModel;
+using System.Text.Json;
+
+namespace MagicSettings.Share;
+
+public enum MagicValueState
+{
+ Value,
+ Null
+}
+
+public enum MagicRemoteValueDurability
+{
+ Sticky,
+ Refreshable,
+ Expiring
+}
+
+public sealed record MagicRemoteValue(
+ MagicValueState State,
+ JsonElement? Value,
+ MagicRemoteValueDurability Durability = MagicRemoteValueDurability.Sticky,
+ DateTimeOffset? ExpiresUtc = null)
+{
+ public static MagicRemoteValue From(T value, MagicRemoteValueDurability durability = MagicRemoteValueDurability.Sticky, DateTimeOffset? expiresUtc = null)
+ => new(MagicValueState.Value, JsonSerializer.SerializeToElement(value), durability, expiresUtc);
+
+ public static MagicRemoteValue ExplicitNull(MagicRemoteValueDurability durability = MagicRemoteValueDurability.Sticky, DateTimeOffset? expiresUtc = null)
+ => new(MagicValueState.Null, null, durability, expiresUtc);
+}
+
+public sealed record MagicRemoteSnapshot(
+ long Revision,
+ DateTimeOffset IssuedUtc,
+ IReadOnlyDictionary Values)
+{
+ public static MagicRemoteSnapshot Empty { get; } = new(0, DateTimeOffset.MinValue, new ReadOnlyDictionary(new Dictionary()));
+}
+
+public sealed record MagicSettingManifestEntry(
+ string Path,
+ string Type,
+ bool Nullable,
+ bool Sensitive,
+ bool RemoteOverrideAllowed,
+ string? Description = null);
+
+public sealed record MagicSettingsSchemaManifest(
+ string ApplicationId,
+ string ApplicationVersion,
+ int SchemaVersion,
+ string SchemaFingerprint,
+ IReadOnlyList Settings);
+
+public enum MagicMigrationReviewSeverity
+{
+ Information,
+ Warning,
+ Destructive
+}
+
+public sealed record MagicMigrationReviewItem(
+ string Path,
+ string Operation,
+ MagicMigrationReviewSeverity Severity,
+ string Reason,
+ string? ProposedPath = null);
+
+public sealed record MagicSettingsMigrationReport(
+ int FromVersion,
+ int ToVersion,
+ IReadOnlyList SafeOperations,
+ IReadOnlyList ReviewItems);
diff --git a/MagicSettings/MagicSettings.Share/ControlPlaneContracts.cs b/MagicSettings/MagicSettings.Share/ControlPlaneContracts.cs
new file mode 100644
index 0000000..ff19f55
--- /dev/null
+++ b/MagicSettings/MagicSettings.Share/ControlPlaneContracts.cs
@@ -0,0 +1,58 @@
+namespace MagicSettings.Share;
+
+public enum MagicControlPlaneEndpointSource
+{
+ None,
+ CodeFallback,
+ PersistentSettings,
+ EnvironmentVariable,
+ RuntimeOverride
+}
+
+public sealed record MagicResolvedControlPlaneEndpoint(Uri? Endpoint, MagicControlPlaneEndpointSource Source)
+{
+ public static MagicResolvedControlPlaneEndpoint None { get; } = new(null, MagicControlPlaneEndpointSource.None);
+}
+
+public enum MagicControlPlaneState
+{
+ Disabled,
+ Configured,
+ Connecting,
+ PendingApproval,
+ Active,
+ Disconnected,
+ Faulted
+}
+
+public enum MagicControlPlaneTrustMode
+{
+ SystemTls,
+ PinnedPublicKey
+}
+
+public sealed record MagicControlPlaneTrust(
+ MagicControlPlaneTrustMode Mode,
+ string AuthorityId,
+ string? PinnedPublicKeyFingerprint = null)
+{
+ public static MagicControlPlaneTrust SystemTls(string authorityId) => new(MagicControlPlaneTrustMode.SystemTls, authorityId);
+ public static MagicControlPlaneTrust Pinned(string authorityId, string fingerprint) => new(MagicControlPlaneTrustMode.PinnedPublicKey, authorityId, fingerprint);
+}
+
+public sealed record MagicSettingsSyncRequest(
+ MagicNodeIdentityDescriptor Identity,
+ MagicAuthenticationProof Proof,
+ MagicSettingsSchemaManifest Manifest,
+ long LastRemoteRevision,
+ MagicSettingsMigrationReport? MigrationReport = null,
+ MagicIdentityContinuityProof? IdentityContinuityProof = null);
+
+public sealed record MagicSettingsSyncResponse(
+ MagicControlPlaneState State,
+ MagicRemoteSnapshot Snapshot,
+ string? Message = null,
+ TimeSpan? SuggestedPollInterval = null);
+
+public sealed record MagicSecretRequest(Guid NodeId, Guid CredentialId, string Name, MagicAuthenticationProof Proof);
+public sealed record MagicSecretResponse(bool Found, string? Value, DateTimeOffset? ExpiresUtc = null);
diff --git a/MagicSettings/MagicSettings.Share/MagicSettings.Share.csproj b/MagicSettings/MagicSettings.Share/MagicSettings.Share.csproj
index 237d661..ecaa002 100644
--- a/MagicSettings/MagicSettings.Share/MagicSettings.Share.csproj
+++ b/MagicSettings/MagicSettings.Share/MagicSettings.Share.csproj
@@ -1,9 +1,6 @@
-
-
-
- net10.0
- enable
- enable
-
-
+
+
+ MagicSettings.Share
+ Shared protocol, schema, and security contracts for MagicSettings clients and control-plane implementations.
+
diff --git a/MagicSettings/MagicSettings.Share/ProofCodec.cs b/MagicSettings/MagicSettings.Share/ProofCodec.cs
new file mode 100644
index 0000000..09dba9e
--- /dev/null
+++ b/MagicSettings/MagicSettings.Share/ProofCodec.cs
@@ -0,0 +1,26 @@
+using System.Text;
+using System.Text.Json;
+
+namespace MagicSettings.Share;
+
+public static class MagicNodeProofCodec
+{
+ private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web);
+
+ public static string Encode(MagicAuthenticationProof proof)
+ => Base64UrlEncode(JsonSerializer.SerializeToUtf8Bytes(proof, Json));
+
+ public static MagicAuthenticationProof Decode(string encoded)
+ => JsonSerializer.Deserialize(Base64UrlDecode(encoded), Json)
+ ?? throw new FormatException("The MagicSettings authentication proof is invalid.");
+
+ public static string Base64UrlEncode(ReadOnlySpan value)
+ => Convert.ToBase64String(value).TrimEnd('=').Replace('+', '-').Replace('/', '_');
+
+ public static byte[] Base64UrlDecode(string value)
+ {
+ var padded = value.Replace('-', '+').Replace('_', '/');
+ padded += new string('=', (4 - padded.Length % 4) % 4);
+ return Convert.FromBase64String(padded);
+ }
+}
diff --git a/MagicSettings/MagicSettings.Share/SecretProof.cs b/MagicSettings/MagicSettings.Share/SecretProof.cs
new file mode 100644
index 0000000..de4b359
--- /dev/null
+++ b/MagicSettings/MagicSettings.Share/SecretProof.cs
@@ -0,0 +1,13 @@
+using System.Security.Cryptography;
+using System.Text;
+
+namespace MagicSettings.Share;
+
+public static class MagicSecretProof
+{
+ public static string ComputeBodySha256(string name)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(name);
+ return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(name))).ToLowerInvariant();
+ }
+}
diff --git a/MagicSettings/MagicSettings.Share/SecurityContracts.cs b/MagicSettings/MagicSettings.Share/SecurityContracts.cs
new file mode 100644
index 0000000..d158f4a
--- /dev/null
+++ b/MagicSettings/MagicSettings.Share/SecurityContracts.cs
@@ -0,0 +1,80 @@
+namespace MagicSettings.Share;
+
+public enum MagicCredentialKind
+{
+ EcdsaP256
+}
+
+public enum MagicCredentialStatus
+{
+ Pending,
+ Approved,
+ Retiring,
+ Revoked
+}
+
+public sealed record MagicNodeIdentityDescriptor(
+ Guid NodeId,
+ Guid CredentialId,
+ MagicCredentialKind CredentialKind,
+ string SignatureAlgorithm,
+ string PublicKey,
+ string Fingerprint,
+ DateTimeOffset CreatedUtc);
+
+public sealed record MagicAuthenticationRequest(
+ string Audience,
+ string Method,
+ Uri Uri,
+ string BodySha256,
+ TimeSpan? ValidFor = null);
+
+public sealed record MagicAuthenticationProof(
+ string Version,
+ Guid NodeId,
+ Guid CredentialId,
+ string Audience,
+ string Method,
+ string Target,
+ string BodySha256,
+ DateTimeOffset IssuedUtc,
+ DateTimeOffset ExpiresUtc,
+ string Nonce,
+ string Signature);
+
+public sealed record MagicIdentityContinuityProof(
+ MagicNodeIdentityDescriptor PreviousIdentity,
+ MagicNodeIdentityDescriptor NewIdentity,
+ DateTimeOffset IssuedUtc,
+ string Nonce,
+ string Signature);
+
+public enum MagicIdentityChangeKind
+{
+ Rotated,
+ Reset,
+ RecoveredAfterLoss
+}
+
+public sealed record MagicIdentityChange(
+ MagicIdentityChangeKind Kind,
+ MagicNodeIdentityDescriptor Current,
+ MagicNodeIdentityDescriptor? Previous,
+ MagicIdentityContinuityProof? ContinuityProof,
+ string Reason);
+
+public sealed record MagicIdentityResetRequest(string Reason, bool ConfirmDestructiveReset);
+
+public sealed record MagicProofVerificationRequest(
+ MagicAuthenticationProof Proof,
+ string ExpectedAudience,
+ string Method,
+ Uri Uri,
+ string BodySha256,
+ DateTimeOffset NowUtc);
+
+public sealed record MagicProofVerificationResult(bool IsValid, string? Error)
+{
+ public static MagicProofVerificationResult Valid { get; } = new(true, null);
+ public static MagicProofVerificationResult Invalid(string error) => new(false, error);
+}
diff --git a/MagicSettings/MagicSettings.Share/SyncProof.cs b/MagicSettings/MagicSettings.Share/SyncProof.cs
new file mode 100644
index 0000000..5e92a1a
--- /dev/null
+++ b/MagicSettings/MagicSettings.Share/SyncProof.cs
@@ -0,0 +1,50 @@
+using System.Security.Cryptography;
+using System.Text.Json;
+
+namespace MagicSettings.Share;
+
+///
+/// Produces the deterministic payload hash signed for a control-plane synchronization request.
+/// The proof itself is intentionally excluded so the request can be signed without a circular dependency.
+///
+public static class MagicSettingsSyncProof
+{
+ private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web);
+
+ public static string ComputeBodySha256(
+ MagicNodeIdentityDescriptor identity,
+ MagicSettingsSchemaManifest manifest,
+ long lastRemoteRevision,
+ MagicSettingsMigrationReport? migrationReport,
+ MagicIdentityContinuityProof? identityContinuityProof = null)
+ {
+ ArgumentNullException.ThrowIfNull(identity);
+ ArgumentNullException.ThrowIfNull(manifest);
+
+ var payload = new MagicSettingsSyncSigningPayload(
+ identity.NodeId,
+ identity.CredentialId,
+ identity.PublicKey,
+ manifest.ApplicationId,
+ manifest.ApplicationVersion,
+ manifest.SchemaVersion,
+ manifest.SchemaFingerprint,
+ lastRemoteRevision,
+ migrationReport,
+ identityContinuityProof);
+
+ return Convert.ToHexString(SHA256.HashData(JsonSerializer.SerializeToUtf8Bytes(payload, Json))).ToLowerInvariant();
+ }
+
+ private sealed record MagicSettingsSyncSigningPayload(
+ Guid NodeId,
+ Guid CredentialId,
+ string PublicKey,
+ string ApplicationId,
+ string ApplicationVersion,
+ int SchemaVersion,
+ string SchemaFingerprint,
+ long LastRemoteRevision,
+ MagicSettingsMigrationReport? MigrationReport,
+ MagicIdentityContinuityProof? IdentityContinuityProof);
+}
diff --git a/MagicSettings/MagicSettings.Tests/MagicSettings.Tests.csproj b/MagicSettings/MagicSettings.Tests/MagicSettings.Tests.csproj
index 68b9f9b..eee3404 100644
--- a/MagicSettings/MagicSettings.Tests/MagicSettings.Tests.csproj
+++ b/MagicSettings/MagicSettings.Tests/MagicSettings.Tests.csproj
@@ -1,23 +1,24 @@
-
-
-
- net10.0
- latest
- enable
- enable
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ false
+ true
+ false
+ false
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
diff --git a/MagicSettings/MagicSettings.Tests/Source.01.cs b/MagicSettings/MagicSettings.Tests/Source.01.cs
new file mode 100644
index 0000000..d5fc0ec
--- /dev/null
+++ b/MagicSettings/MagicSettings.Tests/Source.01.cs
@@ -0,0 +1,256 @@
+// Consolidated source file. See repository history and wiki for subsystem boundaries.
+global using Xunit;
+using MagicSettings.Server;
+using MagicSettings.Share;
+using System.Text.Json.Nodes;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace MagicSettings.Tests
+{
+public sealed class CredentialAdministrationTests
+{
+ [Fact]
+ public async Task RevokedCredentialCanNoLongerAuthenticate()
+ {
+ using var directory = new TemporaryDirectory();
+ var manager = new MagicNodeIdentityManager(new FileMagicNodeIdentityStore(Path.Combine(directory.Path, "identity.json")));
+ var identity = await manager.GetCurrentAsync();
+ var registry = new InMemoryMagicCredentialRegistry();
+ await registry.UpsertAsync(new(identity.NodeId, identity.CredentialId, identity.PublicKey, MagicCredentialStatus.Approved, DateTimeOffset.UtcNow));
+ var administration = new MagicCredentialAdministrationService(registry);
+ Assert.True(await administration.RevokeAsync(identity.NodeId, identity.CredentialId));
+
+ var uri = new Uri("https://api.example/resource");
+ var proof = await manager.CreateProofAsync(new("Resource.Api", "GET", uri, MagicHash.EmptySha256));
+ var result = await new MagicNodeProofVerifier(registry, new InMemoryMagicReplayCache()).VerifyAsync(
+ new(proof, "Resource.Api", "GET", uri, MagicHash.EmptySha256, DateTimeOffset.UtcNow));
+
+ Assert.False(result.IsValid);
+ Assert.Equal("The credential is Revoked.", result.Error);
+ }
+}
+}
+namespace MagicSettings.Tests
+{
+public sealed class CredentialRotationTests
+{
+ [Fact]
+ public async Task ServerCanApproveRotatedCredentialWithoutReceivingPrivateKey()
+ {
+ using var directory = new TemporaryDirectory();
+ var manager = new MagicNodeIdentityManager(new FileMagicNodeIdentityStore(Path.Combine(directory.Path, "identity.json")));
+ var initial = await manager.GetCurrentAsync();
+ var registry = new InMemoryMagicCredentialRegistry();
+ await registry.UpsertAsync(new(initial.NodeId, initial.CredentialId, initial.PublicKey, MagicCredentialStatus.Approved, DateTimeOffset.UtcNow));
+ var rotation = await manager.RotateAsync("scheduled");
+
+ var result = await new MagicCredentialRotationService(registry).ApplyAsync(rotation.ContinuityProof!, autoApproveNewCredential: true);
+ var oldCredential = await registry.FindAsync(initial.NodeId, initial.CredentialId);
+ var newCredential = await registry.FindAsync(rotation.Current.NodeId, rotation.Current.CredentialId);
+
+ Assert.True(result.IsValid);
+ Assert.Equal(MagicCredentialStatus.Retiring, oldCredential!.Status);
+ Assert.Equal(MagicCredentialStatus.Approved, newCredential!.Status);
+ }
+ [Fact]
+ public async Task SyncCanCarryContinuityProofAndAutoApproveRotatedCredential()
+ {
+ using var directory = new TemporaryDirectory();
+ var manager = new MagicNodeIdentityManager(new FileMagicNodeIdentityStore(Path.Combine(directory.Path, "identity.json")));
+ var initial = await manager.GetCurrentAsync();
+ var registry = new InMemoryMagicCredentialRegistry();
+ await registry.UpsertAsync(new(initial.NodeId, initial.CredentialId, initial.PublicKey, MagicCredentialStatus.Approved, DateTimeOffset.UtcNow));
+ var change = await manager.RotateAsync("scheduled");
+ var records = new InMemoryMagicNodeRemoteRecordStore();
+ var verifier = new MagicNodeProofVerifier(registry, new InMemoryMagicReplayCache());
+ var service = new MagicSettingsSyncService(registry, records, verifier, autoApproveRotatedCredentials: true);
+ var uri = new Uri("https://control.example/magicsettings/sync");
+ var manifest = new MagicSettingsSchemaManifest("TestApp", "1.0.0", 1, "fingerprint", []);
+ var bodyHash = MagicSettingsSyncProof.ComputeBodySha256(change.Current, manifest, 0, null, change.ContinuityProof);
+ var proof = await manager.CreateProofAsync(new("Control.Api", "POST", uri, bodyHash));
+
+ var response = await service.SynchronizeAsync(
+ new(change.Current, proof, manifest, 0, null, change.ContinuityProof),
+ "Control.Api",
+ uri);
+
+ Assert.Equal(MagicControlPlaneState.Active, response.State);
+ Assert.Equal(
+ MagicCredentialStatus.Approved,
+ (await registry.FindAsync(change.Current.NodeId, change.Current.CredentialId))!.Status);
+ }
+
+}
+}
+namespace MagicSettings.Tests
+{
+public sealed class EndpointBootstrapTests
+{
+ [Fact]
+ public void Resolver_UsesEnvironmentBeforePersistentAndFallback()
+ {
+ const string variable = "MAGICSETTINGS_TEST_CONTROL_PLANE";
+ var previous = Environment.GetEnvironmentVariable(variable);
+ try
+ {
+ Environment.SetEnvironmentVariable(variable, "https://environment.example/");
+ var options = new MagicSettingsOptions();
+ options.ControlPlane.Bootstrap.EnvironmentVariableName = variable;
+ options.ControlPlane.Bootstrap.PersistentSettingPath = "MagicSettings:ControlPlane:Endpoint";
+ options.ControlPlane.Bootstrap.CodeFallbackEndpoint = new Uri("https://fallback.example/");
+ var document = JsonNode.Parse("""{"MagicSettings":{"ControlPlane":{"Endpoint":"https://file.example/"}}}""")!.AsObject();
+
+ var resolved = new MagicControlPlaneEndpointResolver().Resolve(options, document);
+
+ Assert.Equal(MagicControlPlaneEndpointSource.EnvironmentVariable, resolved.Source);
+ Assert.Equal("https://environment.example/", resolved.Endpoint!.AbsoluteUri);
+ }
+ finally
+ {
+ Environment.SetEnvironmentVariable(variable, previous);
+ }
+ }
+
+ [Fact]
+ public void Resolver_UsesPersistentFileWithoutConsultingRemoteState()
+ {
+ var options = new MagicSettingsOptions();
+ options.ControlPlane.Bootstrap.EnvironmentVariableName = "MAGICSETTINGS_TEST_MISSING_ENDPOINT";
+ options.ControlPlane.Bootstrap.PersistentSettingPath = "MagicSettings:ControlPlane:Endpoint";
+ var local = JsonNode.Parse("""{"MagicSettings":{"ControlPlane":{"Endpoint":"https://local.example/"}}}""")!.AsObject();
+
+ var resolved = new MagicControlPlaneEndpointResolver().Resolve(options, local);
+
+ Assert.Equal(MagicControlPlaneEndpointSource.PersistentSettings, resolved.Source);
+ Assert.Equal("https://local.example/", resolved.Endpoint!.AbsoluteUri);
+ }
+
+ [Fact]
+ public void Resolver_RejectsNonLoopbackHttpUnlessExplicitlyAllowed()
+ {
+ var options = new MagicSettingsOptions();
+ options.ControlPlane.Bootstrap.CodeFallbackEndpoint = new Uri("http://control.example/");
+
+ Assert.Throws(() =>
+ new MagicControlPlaneEndpointResolver().Resolve(options, new JsonObject()));
+
+ options.ControlPlane.Bootstrap.AllowInsecureHttp = true;
+ var resolved = new MagicControlPlaneEndpointResolver().Resolve(options, new JsonObject());
+ Assert.Equal("http://control.example/", resolved.Endpoint!.AbsoluteUri);
+ }
+
+ [Fact]
+ public void RuntimeOverride_IsHighestPriority()
+ {
+ var options = new MagicSettingsOptions();
+ options.ControlPlane.Bootstrap.CodeFallbackEndpoint = new Uri("https://fallback.example/");
+ var resolved = new MagicControlPlaneEndpointResolver().Resolve(options, new JsonObject(), new Uri("https://runtime.example/"));
+ Assert.Equal(MagicControlPlaneEndpointSource.RuntimeOverride, resolved.Source);
+ Assert.Equal("https://runtime.example/", resolved.Endpoint!.AbsoluteUri);
+ }
+}
+}
+namespace MagicSettings.Tests
+{
+public sealed class EnrollmentTests
+{
+ [Fact]
+ public async Task UnknownNodeMustProvePossessionBeforeBeingRegisteredAsPending()
+ {
+ using var directory = new TemporaryDirectory();
+ var manager = new MagicNodeIdentityManager(new FileMagicNodeIdentityStore(Path.Combine(directory.Path, "identity.json")));
+ var identity = await manager.GetCurrentAsync();
+ var registry = new InMemoryMagicCredentialRegistry();
+ var service = new MagicSettingsSyncService(
+ registry,
+ new InMemoryMagicNodeRemoteRecordStore(),
+ new MagicNodeProofVerifier(registry, new InMemoryMagicReplayCache()));
+ var uri = new Uri("https://control.example/magicsettings/sync");
+ var manifest = new MagicSettingsSchemaManifest("TestApp", "1.0.0", 1, "fingerprint", []);
+ var bodyHash = MagicSettingsSyncProof.ComputeBodySha256(identity, manifest, 0, null);
+ var proof = await manager.CreateProofAsync(new("Control.Api", "POST", uri, bodyHash));
+
+ var response = await service.SynchronizeAsync(new(identity, proof, manifest, 0), "Control.Api", uri);
+ var registered = await registry.FindAsync(identity.NodeId, identity.CredentialId);
+
+ Assert.Equal(MagicControlPlaneState.PendingApproval, response.State);
+ Assert.Equal(MagicCredentialStatus.Pending, registered!.Status);
+ }
+
+ [Fact]
+ public async Task UnknownNodeWithTamperedManifestIsRejected()
+ {
+ using var directory = new TemporaryDirectory();
+ var manager = new MagicNodeIdentityManager(new FileMagicNodeIdentityStore(Path.Combine(directory.Path, "identity.json")));
+ var identity = await manager.GetCurrentAsync();
+ var registry = new InMemoryMagicCredentialRegistry();
+ var service = new MagicSettingsSyncService(
+ registry,
+ new InMemoryMagicNodeRemoteRecordStore(),
+ new MagicNodeProofVerifier(registry, new InMemoryMagicReplayCache()));
+ var uri = new Uri("https://control.example/magicsettings/sync");
+ var signedManifest = new MagicSettingsSchemaManifest("TestApp", "1.0.0", 1, "fingerprint-a", []);
+ var sentManifest = signedManifest with { SchemaFingerprint = "fingerprint-b" };
+ var bodyHash = MagicSettingsSyncProof.ComputeBodySha256(identity, signedManifest, 0, null);
+ var proof = await manager.CreateProofAsync(new("Control.Api", "POST", uri, bodyHash));
+
+ var response = await service.SynchronizeAsync(new(identity, proof, sentManifest, 0), "Control.Api", uri);
+
+ Assert.Equal(MagicControlPlaneState.Faulted, response.State);
+ Assert.Null(await registry.FindAsync(identity.NodeId, identity.CredentialId));
+ }
+}
+}
+namespace MagicSettings.Tests
+{
+public sealed class IdentityTests
+{
+ [Fact]
+ public async Task Identity_IsStableAcrossManagerInstances()
+ {
+ using var directory = new TemporaryDirectory();
+ var path = Path.Combine(directory.Path, "identity.json");
+ var first = new MagicNodeIdentityManager(new FileMagicNodeIdentityStore(path));
+ var firstIdentity = await first.GetCurrentAsync();
+ var second = new MagicNodeIdentityManager(new FileMagicNodeIdentityStore(path));
+ var secondIdentity = await second.GetCurrentAsync();
+
+ Assert.Equal(firstIdentity.NodeId, secondIdentity.NodeId);
+ Assert.Equal(firstIdentity.CredentialId, secondIdentity.CredentialId);
+ Assert.Equal(firstIdentity.Fingerprint, secondIdentity.Fingerprint);
+ }
+
+ [Fact]
+ public async Task Rotation_KeepsNodeAndProducesVerifiableContinuityProof()
+ {
+ using var directory = new TemporaryDirectory();
+ var manager = new MagicNodeIdentityManager(new FileMagicNodeIdentityStore(Path.Combine(directory.Path, "identity.json")));
+ var before = await manager.GetCurrentAsync();
+
+ var change = await manager.RotateAsync("scheduled rotation");
+
+ Assert.Equal(MagicIdentityChangeKind.Rotated, change.Kind);
+ Assert.Equal(before.NodeId, change.Current.NodeId);
+ Assert.NotEqual(before.CredentialId, change.Current.CredentialId);
+ Assert.NotNull(change.ContinuityProof);
+ Assert.True(MagicNodeProofVerifier.VerifyContinuity(change.ContinuityProof!));
+ }
+
+ [Fact]
+ public async Task Reset_ChangesNodeAndRequiresExplicitConfirmation()
+ {
+ using var directory = new TemporaryDirectory();
+ var manager = new MagicNodeIdentityManager(new FileMagicNodeIdentityStore(Path.Combine(directory.Path, "identity.json")));
+ var before = await manager.GetCurrentAsync();
+
+ await Assert.ThrowsAsync(async () =>
+ await manager.ResetAsync(new("test", false)));
+
+ var change = await manager.ResetAsync(new("compromised", true));
+ Assert.Equal(MagicIdentityChangeKind.Reset, change.Kind);
+ Assert.NotEqual(before.NodeId, change.Current.NodeId);
+ Assert.Null(change.ContinuityProof);
+ }
+}
+}
diff --git a/MagicSettings/MagicSettings.Tests/Source.02.cs b/MagicSettings/MagicSettings.Tests/Source.02.cs
new file mode 100644
index 0000000..fc75ce7
--- /dev/null
+++ b/MagicSettings/MagicSettings.Tests/Source.02.cs
@@ -0,0 +1,378 @@
+// Consolidated source file. See repository history and wiki for subsystem boundaries.
+global using Xunit;
+using MagicSettings.Server;
+using MagicSettings.Share;
+using System.Text.Json.Nodes;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace MagicSettings.Tests
+{
+public sealed class InitializationTests
+{
+ [Fact]
+ public async Task Initialization_GeneratesFileAndPreservesExistingValuesWhileAddingDefaults()
+ {
+ using var directory = new TemporaryDirectory();
+ var path = Path.Combine(directory.Path, "settings.json");
+ var builder = new HostApplicationBuilder();
+ await builder.AddMagicSettingsAsync(configure: options =>
+ {
+ options.Path = path;
+ options.Template.Database.Host = "template-host";
+ options.Template.Database.Port = 5432;
+ });
+
+ var document = JsonNode.Parse(await File.ReadAllTextAsync(path))!.AsObject();
+ document["Database"]!["Host"] = "operator-host";
+ document["Database"]!.AsObject().Remove("Port");
+ await File.WriteAllTextAsync(path, document.ToJsonString(new() { WriteIndented = true }));
+
+ var secondBuilder = new HostApplicationBuilder();
+ await secondBuilder.AddMagicSettingsAsync(configure: options =>
+ {
+ options.Path = path;
+ options.Template.Database.Host = "new-template-host";
+ options.Template.Database.Port = 7777;
+ });
+
+ var reconciled = JsonNode.Parse(await File.ReadAllTextAsync(path))!.AsObject();
+ Assert.Equal("operator-host", reconciled["Database"]!["Host"]!.GetValue());
+ Assert.Equal(7777, reconciled["Database"]!["Port"]!.GetValue());
+ }
+
+ [Fact]
+ public async Task RemoteSnapshot_OverridesEnvironmentAndFileWithoutPersisting()
+ {
+ using var directory = new TemporaryDirectory();
+ var path = Path.Combine(directory.Path, "settings.json");
+ var transport = new FakeTransport("Database:Host", MagicRemoteValue.From("remote-host"));
+ var previous = Environment.GetEnvironmentVariable("MagicSettings__Database__Host");
+ Environment.SetEnvironmentVariable("MagicSettings__Database__Host", "environment-host");
+ try
+ {
+ var builder = new HostApplicationBuilder();
+ await builder.AddMagicSettingsAsync(configure: options =>
+ {
+ options.Path = path;
+ options.Template.Database.Host = "file-host";
+ options.ControlPlaneTransport = transport;
+ });
+ await using var provider = builder.Services.BuildServiceProvider();
+ var controlPlane = provider.GetRequiredService();
+ await controlPlane.ConfigureAsync(new Uri("https://control.example/"), MagicControlPlaneTrust.SystemTls("Control.Api"));
+ var settings = provider.GetRequiredService>();
+
+ Assert.Equal("remote-host", settings.Current.Database.Host);
+ var file = JsonNode.Parse(await File.ReadAllTextAsync(path))!.AsObject();
+ Assert.Equal("file-host", file["Database"]!["Host"]!.GetValue());
+ }
+ finally
+ {
+ Environment.SetEnvironmentVariable("MagicSettings__Database__Host", previous);
+ }
+ }
+
+ [Fact]
+ public async Task RemoteSnapshot_CannotOverrideControlPlaneBootstrapEndpoint()
+ {
+ using var directory = new TemporaryDirectory();
+ var path = Path.Combine(directory.Path, "settings.json");
+ var transport = new FakeTransport(
+ "MagicSettings:ControlPlane:Endpoint",
+ MagicRemoteValue.From("https://attacker.example/"));
+ var builder = new HostApplicationBuilder();
+ await builder.AddMagicSettingsAsync(configure: options =>
+ {
+ options.Path = path;
+ options.Template.MagicSettings.ControlPlane.Endpoint = "https://trusted-local.example/";
+ options.ControlPlaneTransport = transport;
+ });
+ await using var provider = builder.Services.BuildServiceProvider();
+ await provider.GetRequiredService().ConfigureAsync(
+ new Uri("https://control.example/"),
+ MagicControlPlaneTrust.SystemTls("Control.Api"));
+
+ var settings = provider.GetRequiredService>();
+ Assert.Equal("https://trusted-local.example/", settings.Current.MagicSettings.ControlPlane.Endpoint);
+ }
+
+ [Fact]
+ public async Task DevelopmentRequiresExplicitArrayPolicy()
+ {
+ using var directory = new TemporaryDirectory();
+ var path = Path.Combine(directory.Path, "settings.json");
+ var first = new HostApplicationBuilder(new HostApplicationBuilderSettings { EnvironmentName = "Development" });
+ await first.AddMagicSettingsAsync(configure: options =>
+ {
+ options.Path = path;
+ options.Environment = "Development";
+ options.Template.Origins = ["https://one.example"];
+ });
+
+ var second = new HostApplicationBuilder(new HostApplicationBuilderSettings { EnvironmentName = "Development" });
+ await Assert.ThrowsAsync(async () =>
+ await second.AddMagicSettingsAsync(configure: options =>
+ {
+ options.Path = path;
+ options.Environment = "Development";
+ options.Template.Origins = ["https://one.example", "https://two.example"];
+ }));
+ }
+
+ private sealed class FakeTransport : IMagicControlPlaneTransport
+ {
+ private readonly string _path;
+ private readonly MagicRemoteValue _value;
+ public FakeTransport(string path, MagicRemoteValue value)
+ {
+ _path = path;
+ _value = value;
+ }
+
+ public ValueTask SynchronizeAsync(Uri endpoint, MagicControlPlaneTrust trust, MagicSettingsSyncRequest request, CancellationToken cancellationToken = default)
+ => ValueTask.FromResult(new MagicSettingsSyncResponse(
+ MagicControlPlaneState.Active,
+ new MagicRemoteSnapshot(1, DateTimeOffset.UtcNow, new Dictionary
+ {
+ [_path] = _value
+ })));
+ }
+}
+}
+namespace MagicSettings.Tests
+{
+public sealed class MigrationTests
+{
+ [Fact]
+ public async Task SequentialMigrationRenamesAndTransformsWithoutDeletingRemoteHistory()
+ {
+ using var directory = new TemporaryDirectory();
+ var path = Path.Combine(directory.Path, "settings.json");
+ await File.WriteAllTextAsync(path, """
+ {
+ "Database": {
+ "Server": "old-host",
+ "Port": "7443"
+ },
+ "$magicSettings": {
+ "schemaVersion": 1
+ }
+ }
+ """);
+
+ var builder = new HostApplicationBuilder();
+ await builder.AddMagicSettingsAsync(configure: options =>
+ {
+ options.Path = path;
+ options.SchemaVersion = 2;
+ options.Migrations.Add(new VersionOneToTwo());
+ });
+
+ var document = JsonNode.Parse(await File.ReadAllTextAsync(path))!.AsObject();
+ Assert.Equal("old-host", document["Database"]!["Host"]!.GetValue());
+ Assert.Equal(7443, document["Database"]!["Port"]!.GetValue());
+ Assert.Equal(2, document["$magicSettings"]!["schemaVersion"]!.GetValue());
+ }
+
+ [Fact]
+ public async Task MissingMigrationFailsStartupInEveryEnvironment()
+ {
+ using var directory = new TemporaryDirectory();
+ var path = Path.Combine(directory.Path, "settings.json");
+ await File.WriteAllTextAsync(path, """{"$magicSettings":{"schemaVersion":1}}""");
+ var builder = new HostApplicationBuilder(new HostApplicationBuilderSettings { EnvironmentName = "Production" });
+
+ await Assert.ThrowsAsync(async () =>
+ await builder.AddMagicSettingsAsync(configure: options =>
+ {
+ options.Path = path;
+ options.SchemaVersion = 2;
+ }));
+ }
+
+ private sealed class VersionOneToTwo : IMagicSettingsMigration
+ {
+ public int FromVersion => 1;
+ public int ToVersion => 2;
+
+ public void Apply(JsonObject document, MagicMigrationContext context)
+ {
+ context.Rename(document, "Database:Server", "Database:Host");
+ context.Transform(
+ document,
+ "Database:Port",
+ node => JsonValue.Create(int.Parse(node!.GetValue(), System.Globalization.CultureInfo.InvariantCulture)),
+ "Convert port from string to integer.",
+ remoteSafeProjection: false);
+ }
+ }
+}
+}
+namespace MagicSettings.Tests
+{
+public sealed class ProofTests
+{
+ [Fact]
+ public async Task Proof_IsAudienceMethodTargetAndBodyBound_AndCannotReplay()
+ {
+ using var directory = new TemporaryDirectory();
+ var manager = new MagicNodeIdentityManager(new FileMagicNodeIdentityStore(Path.Combine(directory.Path, "identity.json")));
+ var identity = await manager.GetCurrentAsync();
+ var registry = new InMemoryMagicCredentialRegistry();
+ await registry.UpsertAsync(new(identity.NodeId, identity.CredentialId, identity.PublicKey, MagicCredentialStatus.Approved, DateTimeOffset.UtcNow));
+ var verifier = new MagicNodeProofVerifier(registry, new InMemoryMagicReplayCache());
+ var uri = new Uri("https://api.example/v1/items?x=1");
+ var bodyHash = MagicHash.Sha256Hex("hello"u8);
+ var proof = await manager.CreateProofAsync(new("Items.Api", "POST", uri, bodyHash));
+ var request = new MagicProofVerificationRequest(proof, "Items.Api", "POST", uri, bodyHash, DateTimeOffset.UtcNow);
+
+ Assert.True((await verifier.VerifyAsync(request)).IsValid);
+ Assert.Equal("The proof nonce has already been used.", (await verifier.VerifyAsync(request)).Error);
+ }
+
+ [Fact]
+ public async Task Proof_RejectsWrongAudienceBeforeNonceConsumption()
+ {
+ using var directory = new TemporaryDirectory();
+ var manager = new MagicNodeIdentityManager(new FileMagicNodeIdentityStore(Path.Combine(directory.Path, "identity.json")));
+ var identity = await manager.GetCurrentAsync();
+ var registry = new InMemoryMagicCredentialRegistry();
+ await registry.UpsertAsync(new(identity.NodeId, identity.CredentialId, identity.PublicKey, MagicCredentialStatus.Approved, DateTimeOffset.UtcNow));
+ var verifier = new MagicNodeProofVerifier(registry, new InMemoryMagicReplayCache());
+ var uri = new Uri("https://api.example/v1/items");
+ var proof = await manager.CreateProofAsync(new("Items.Api", "GET", uri, MagicHash.EmptySha256));
+
+ var wrong = await verifier.VerifyAsync(new(proof, "Admin.Api", "GET", uri, MagicHash.EmptySha256, DateTimeOffset.UtcNow));
+ var correct = await verifier.VerifyAsync(new(proof, "Items.Api", "GET", uri, MagicHash.EmptySha256, DateTimeOffset.UtcNow));
+
+ Assert.False(wrong.IsValid);
+ Assert.True(correct.IsValid);
+ }
+}
+}
+namespace MagicSettings.Tests
+{
+public sealed class SecretTests
+{
+ [Fact]
+ public async Task SecretRequestUsesFreshBoundNodeProof()
+ {
+ using var directory = new TemporaryDirectory();
+ var manager = new MagicNodeIdentityManager(new FileMagicNodeIdentityStore(Path.Combine(directory.Path, "identity.json")));
+ var identity = await manager.GetCurrentAsync();
+ var registry = new InMemoryMagicCredentialRegistry();
+ await registry.UpsertAsync(new(identity.NodeId, identity.CredentialId, identity.PublicKey, MagicCredentialStatus.Approved, DateTimeOffset.UtcNow));
+ var verifier = new MagicNodeProofVerifier(registry, new InMemoryMagicReplayCache());
+ var resolver = new FakeSecretResolver();
+ var service = new MagicSecretService(verifier, resolver);
+ var uri = new Uri("https://control.example/magicsettings/secrets/resolve");
+ const string name = "Database:Password";
+ var proof = await manager.CreateProofAsync(new("Control.Api", "POST", uri, MagicSecretProof.ComputeBodySha256(name)));
+
+ var response = await service.ResolveAsync(new(identity.NodeId, identity.CredentialId, name, proof), "Control.Api", uri);
+
+ Assert.True(response.Found);
+ Assert.Equal("secret-value", response.Value);
+ }
+
+ private sealed class FakeSecretResolver : IMagicSecretResolver
+ {
+ public ValueTask ResolveAsync(Guid nodeId, string name, CancellationToken cancellationToken = default)
+ => ValueTask.FromResult(new MagicSecretResponse(true, "secret-value"));
+ }
+}
+}
+namespace MagicSettings.Tests
+{
+public sealed class ServerSafetyTests
+{
+ [Fact]
+ public async Task SyncService_RetainsDestructiveMigrationAsManualReview()
+ {
+ using var directory = new TemporaryDirectory();
+ var manager = new MagicNodeIdentityManager(new FileMagicNodeIdentityStore(Path.Combine(directory.Path, "identity.json")));
+ var identity = await manager.GetCurrentAsync();
+ var registry = new InMemoryMagicCredentialRegistry();
+ await registry.UpsertAsync(new(identity.NodeId, identity.CredentialId, identity.PublicKey, MagicCredentialStatus.Approved, DateTimeOffset.UtcNow));
+ var records = new InMemoryMagicNodeRemoteRecordStore();
+ var verifier = new MagicNodeProofVerifier(registry, new InMemoryMagicReplayCache());
+ var service = new MagicSettingsSyncService(registry, records, verifier);
+ var uri = new Uri("https://control.example/magicsettings/sync");
+ var report = new MagicSettingsMigrationReport(1, 2, [],
+ [
+ new("Secrets:Old", "remove", MagicMigrationReviewSeverity.Destructive, "Client no longer uses this secret.")
+ ]);
+ var manifest = new MagicSettingsSchemaManifest("TestApp", "1.0.0", 2, "fingerprint", []);
+ var bodyHash = MagicSettingsSyncProof.ComputeBodySha256(identity, manifest, 0, report);
+ var proof = await manager.CreateProofAsync(new("Control.Api", "POST", uri, bodyHash));
+
+ var response = await service.SynchronizeAsync(new(identity, proof, manifest, 0, report), "Control.Api", uri);
+ var stored = await records.GetAsync(identity.NodeId, "TestApp");
+
+ Assert.Equal(MagicControlPlaneState.Active, response.State);
+ Assert.Single(stored!.PendingReviewItems);
+ Assert.Equal("Secrets:Old", stored.PendingReviewItems[0].Path);
+ }
+}
+}
+namespace MagicSettings.Tests
+{
+internal sealed class TemporaryDirectory : IDisposable
+{
+ public TemporaryDirectory()
+ {
+ Path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "MagicSettings.Tests", Guid.NewGuid().ToString("N"));
+ Directory.CreateDirectory(Path);
+ }
+
+ public string Path { get; }
+
+ public void Dispose()
+ {
+ try
+ {
+ Directory.Delete(Path, recursive: true);
+ }
+ catch (IOException)
+ {
+ }
+ catch (UnauthorizedAccessException)
+ {
+ }
+ }
+}
+}
+namespace MagicSettings.Tests
+{
+public sealed class TestSettings
+{
+ public TestApplication Application { get; set; } = new();
+ public TestDatabase Database { get; set; } = new();
+ public TestControlPlane MagicSettings { get; set; } = new();
+ public List Origins { get; set; } = new();
+}
+
+public sealed class TestApplication
+{
+ public string Name { get; set; } = "Test";
+ public bool FeatureEnabled { get; set; }
+}
+
+public sealed class TestDatabase
+{
+ public string Host { get; set; } = "localhost";
+ public string Password { get; set; } = "change-me";
+ public int Port { get; set; } = 5432;
+}
+
+public sealed class TestControlPlane
+{
+ public TestControlPlaneSection ControlPlane { get; set; } = new();
+}
+
+public sealed class TestControlPlaneSection
+{
+ public string? Endpoint { get; set; }
+}
+}
diff --git a/MagicSettings/MagicSettings.Tests/UnitTest1.cs b/MagicSettings/MagicSettings.Tests/UnitTest1.cs
deleted file mode 100644
index c4dc9ef..0000000
--- a/MagicSettings/MagicSettings.Tests/UnitTest1.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace MagicSettings.Tests;
-
-public class Tests
-{
- [SetUp]
- public void Setup()
- {
- }
-
- [Test]
- public void Test1()
- {
- Assert.Pass();
- }
-}
\ No newline at end of file
diff --git a/MagicSettings/MagicSettings.sln b/MagicSettings/MagicSettings.sln
index 0cb58da..28518a5 100644
--- a/MagicSettings/MagicSettings.sln
+++ b/MagicSettings/MagicSettings.sln
@@ -1,34 +1,36 @@
-
Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MagicSettings", "MagicSettings\MagicSettings.csproj", "{C61F3392-5B49-46BB-8BC0-FEEAA830BC5E}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MagicSettings.Tests", "MagicSettings.Tests\MagicSettings.Tests.csproj", "{66496E6F-9EFB-45C2-A981-88D1E315C705}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MagicSettings.Share", "MagicSettings.Share\MagicSettings.Share.csproj", "{C492C153-D111-4874-93B3-0E41A5FDEDB1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MagicSettings.Server", "MagicSettings.Server\MagicSettings.Server.csproj", "{876477AA-F425-456B-9E7A-30CC10F67EF7}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MagicSettings.Share", "MagicSettings.Share\MagicSettings.Share.csproj", "{C492C153-D111-4874-93B3-0E41A5FDEDB1}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MagicSettings.Tests", "MagicSettings.Tests\MagicSettings.Tests.csproj", "{66496E6F-9EFB-45C2-A981-88D1E315C705}"
EndProject
Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {C61F3392-5B49-46BB-8BC0-FEEAA830BC5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C61F3392-5B49-46BB-8BC0-FEEAA830BC5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C61F3392-5B49-46BB-8BC0-FEEAA830BC5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C61F3392-5B49-46BB-8BC0-FEEAA830BC5E}.Release|Any CPU.Build.0 = Release|Any CPU
- {66496E6F-9EFB-45C2-A981-88D1E315C705}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {66496E6F-9EFB-45C2-A981-88D1E315C705}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {66496E6F-9EFB-45C2-A981-88D1E315C705}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {66496E6F-9EFB-45C2-A981-88D1E315C705}.Release|Any CPU.Build.0 = Release|Any CPU
- {876477AA-F425-456B-9E7A-30CC10F67EF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {876477AA-F425-456B-9E7A-30CC10F67EF7}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {876477AA-F425-456B-9E7A-30CC10F67EF7}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {876477AA-F425-456B-9E7A-30CC10F67EF7}.Release|Any CPU.Build.0 = Release|Any CPU
- {C492C153-D111-4874-93B3-0E41A5FDEDB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C492C153-D111-4874-93B3-0E41A5FDEDB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C492C153-D111-4874-93B3-0E41A5FDEDB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C492C153-D111-4874-93B3-0E41A5FDEDB1}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C61F3392-5B49-46BB-8BC0-FEEAA830BC5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C61F3392-5B49-46BB-8BC0-FEEAA830BC5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C61F3392-5B49-46BB-8BC0-FEEAA830BC5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C61F3392-5B49-46BB-8BC0-FEEAA830BC5E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C492C153-D111-4874-93B3-0E41A5FDEDB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C492C153-D111-4874-93B3-0E41A5FDEDB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C492C153-D111-4874-93B3-0E41A5FDEDB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C492C153-D111-4874-93B3-0E41A5FDEDB1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {876477AA-F425-456B-9E7A-30CC10F67EF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {876477AA-F425-456B-9E7A-30CC10F67EF7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {876477AA-F425-456B-9E7A-30CC10F67EF7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {876477AA-F425-456B-9E7A-30CC10F67EF7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {66496E6F-9EFB-45C2-A981-88D1E315C705}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {66496E6F-9EFB-45C2-A981-88D1E315C705}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {66496E6F-9EFB-45C2-A981-88D1E315C705}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {66496E6F-9EFB-45C2-A981-88D1E315C705}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
EndGlobal
diff --git a/MagicSettings/MagicSettings/MagicSettings.csproj b/MagicSettings/MagicSettings/MagicSettings.csproj
index 237d661..336d442 100644
--- a/MagicSettings/MagicSettings/MagicSettings.csproj
+++ b/MagicSettings/MagicSettings/MagicSettings.csproj
@@ -1,9 +1,10 @@
-
-
-
- net10.0
- enable
- enable
-
-
+
+
+ MagicSettings
+ Strongly typed configuration with generation, reconciliation, migrations, live overrides, and client-owned control-plane integration.
+
+
+
+
+
diff --git a/MagicSettings/MagicSettings/Source.01.cs b/MagicSettings/MagicSettings/Source.01.cs
new file mode 100644
index 0000000..a067df9
--- /dev/null
+++ b/MagicSettings/MagicSettings/Source.01.cs
@@ -0,0 +1,163 @@
+// Consolidated source file. See repository history and wiki for subsystem boundaries.
+using System.Text.Json.Nodes;
+using MagicSettings.Share;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.Json;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http.Headers;
+using System.Collections.Concurrent;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+using System.Collections;
+using System.Globalization;
+using System.ComponentModel.DataAnnotations;
+
+namespace MagicSettings
+{
+public interface IMagicSettings where TSettings : class
+{
+ TSettings Current { get; }
+ long Revision { get; }
+ event EventHandler>? Changed;
+ MagicSettingExplanation Explain(string path);
+}
+
+public sealed record MagicSettingsChangedEventArgs(TSettings Previous, TSettings Current, long Revision) where TSettings : class;
+
+public sealed record MagicSettingExplanation(
+ string Path,
+ string? EffectiveValue,
+ string EffectiveSource,
+ IReadOnlyList Sources,
+ bool IsSensitive);
+
+public sealed record MagicSettingSourceValue(string Source, bool Present, string? Value);
+
+public interface IMagicSettingsValidator where TSettings : class
+{
+ ValueTask> ValidateAsync(TSettings settings, CancellationToken cancellationToken = default);
+}
+
+public interface IMagicSettingsSourceProvider
+{
+ string Name { get; }
+ int Priority { get; }
+ ValueTask> LoadAsync(CancellationToken cancellationToken = default);
+}
+
+public interface IMagicSettingsMigration
+{
+ int FromVersion { get; }
+ int ToVersion { get; }
+ void Apply(JsonObject document, MagicMigrationContext context);
+}
+
+public interface IMagicSecretProvider
+{
+ ValueTask> GetAsync(string name, CancellationToken cancellationToken = default);
+}
+
+public sealed class MagicSecretLease : IAsyncDisposable
+{
+ public MagicSecretLease(T value, DateTimeOffset? expiresUtc = null)
+ {
+ Value = value;
+ ExpiresUtc = expiresUtc;
+ }
+
+ public T Value { get; }
+ public DateTimeOffset? ExpiresUtc { get; }
+ public ValueTask DisposeAsync() => ValueTask.CompletedTask;
+}
+
+public interface IMagicControlPlaneEndpointResolver
+{
+ MagicResolvedControlPlaneEndpoint Resolve(
+ MagicSettingsOptions options,
+ JsonObject persistentDocument,
+ Uri? runtimeOverride = null) where TSettings : class, new();
+}
+
+public interface IMagicSettingsControlPlane
+{
+ MagicControlPlaneState State { get; }
+ MagicResolvedControlPlaneEndpoint CurrentEndpoint { get; }
+ ValueTask ConfigureAsync(Uri endpoint, MagicControlPlaneTrust trust, CancellationToken cancellationToken = default);
+ ValueTask DisconnectAsync(bool clearRemoteOverrides = false, CancellationToken cancellationToken = default);
+ ValueTask RefreshAsync(CancellationToken cancellationToken = default);
+}
+
+public interface IMagicControlPlaneTransport
+{
+ ValueTask SynchronizeAsync(
+ Uri endpoint,
+ MagicControlPlaneTrust trust,
+ MagicSettingsSyncRequest request,
+ CancellationToken cancellationToken = default);
+}
+
+
+public interface IMagicSecretTransport
+{
+ ValueTask ResolveSecretAsync(
+ Uri endpoint,
+ MagicControlPlaneTrust trust,
+ MagicSecretRequest request,
+ CancellationToken cancellationToken = default);
+}
+
+public interface IMagicNodeAuthenticator
+{
+ ValueTask GetCurrentIdentityAsync(CancellationToken cancellationToken = default);
+ ValueTask CreateProofAsync(MagicAuthenticationRequest request, CancellationToken cancellationToken = default);
+}
+
+public interface IMagicNodeIdentityManager
+{
+ event EventHandler? IdentityChanged;
+ ValueTask GetCurrentAsync(CancellationToken cancellationToken = default);
+ ValueTask RotateAsync(string reason, CancellationToken cancellationToken = default);
+ ValueTask ResetAsync(MagicIdentityResetRequest request, CancellationToken cancellationToken = default);
+}
+
+public interface IMagicNodeIdentityStore
+{
+ ValueTask LoadAsync(CancellationToken cancellationToken = default);
+ ValueTask SaveAsync(MagicStoredNodeIdentity identity, CancellationToken cancellationToken = default);
+ ValueTask DeleteAsync(CancellationToken cancellationToken = default);
+}
+
+public sealed record MagicStoredNodeIdentity(
+ Guid NodeId,
+ Guid CredentialId,
+ string PublicKey,
+ string PrivateKey,
+ DateTimeOffset CreatedUtc);
+}
+namespace MagicSettings
+{
+[AttributeUsage(AttributeTargets.Property)]
+public sealed class MagicSensitiveAttribute : Attribute
+{
+}
+
+[AttributeUsage(AttributeTargets.Property)]
+public sealed class MagicRemoteOverrideAttribute : Attribute
+{
+ public MagicRemoteOverrideAttribute(bool allowed = true) => Allowed = allowed;
+ public bool Allowed { get; }
+}
+
+[AttributeUsage(AttributeTargets.Property)]
+public sealed class MagicSettingDescriptionAttribute : Attribute
+{
+ public MagicSettingDescriptionAttribute(string description) => Description = description;
+ public string Description { get; }
+}
+}
diff --git a/MagicSettings/MagicSettings/Source.02.cs b/MagicSettings/MagicSettings/Source.02.cs
new file mode 100644
index 0000000..a916a75
--- /dev/null
+++ b/MagicSettings/MagicSettings/Source.02.cs
@@ -0,0 +1,379 @@
+// Consolidated source file. See repository history and wiki for subsystem boundaries.
+using System.Text.Json.Nodes;
+using MagicSettings.Share;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.Json;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http.Headers;
+using System.Collections.Concurrent;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+using System.Collections;
+using System.Globalization;
+using System.ComponentModel.DataAnnotations;
+
+namespace MagicSettings
+{
+public sealed class MagicControlPlaneEndpointResolver : IMagicControlPlaneEndpointResolver
+{
+ public MagicResolvedControlPlaneEndpoint Resolve(
+ MagicSettingsOptions options,
+ JsonObject persistentDocument,
+ Uri? runtimeOverride = null) where TSettings : class, new()
+ {
+ if (runtimeOverride is not null)
+ {
+ return new(Validate(runtimeOverride, "runtime override", options.ControlPlane.Bootstrap.AllowInsecureHttp), MagicControlPlaneEndpointSource.RuntimeOverride);
+ }
+
+ var environmentValue = Environment.GetEnvironmentVariable(options.ControlPlane.Bootstrap.EnvironmentVariableName);
+ if (!string.IsNullOrWhiteSpace(environmentValue))
+ {
+ return new(ParseAbsolute(environmentValue, options.ControlPlane.Bootstrap.EnvironmentVariableName, options.ControlPlane.Bootstrap.AllowInsecureHttp), MagicControlPlaneEndpointSource.EnvironmentVariable);
+ }
+
+ if (MagicJsonPath.TryGet(persistentDocument, options.ControlPlane.Bootstrap.PersistentSettingPath, out var node)
+ && node is JsonValue value
+ && value.TryGetValue(out var persistentValue)
+ && !string.IsNullOrWhiteSpace(persistentValue))
+ {
+ return new(ParseAbsolute(persistentValue, options.ControlPlane.Bootstrap.PersistentSettingPath, options.ControlPlane.Bootstrap.AllowInsecureHttp), MagicControlPlaneEndpointSource.PersistentSettings);
+ }
+
+ return options.ControlPlane.Bootstrap.CodeFallbackEndpoint is { } fallback
+ ? new(Validate(fallback, "code fallback", options.ControlPlane.Bootstrap.AllowInsecureHttp), MagicControlPlaneEndpointSource.CodeFallback)
+ : MagicResolvedControlPlaneEndpoint.None;
+ }
+
+ private static Uri ParseAbsolute(string value, string source, bool allowInsecureHttp)
+ {
+ if (!Uri.TryCreate(value, UriKind.Absolute, out var endpoint))
+ {
+ throw new InvalidOperationException($"Control-plane endpoint from '{source}' must be an absolute URI.");
+ }
+
+ return Validate(endpoint, source, allowInsecureHttp);
+ }
+
+ internal static Uri Validate(Uri endpoint, string source, bool allowInsecureHttp)
+ {
+ if (!string.Equals(endpoint.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)
+ && !string.Equals(endpoint.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase))
+ {
+ throw new InvalidOperationException($"Control-plane endpoint from '{source}' must use HTTP or HTTPS.");
+ }
+
+ if (string.Equals(endpoint.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)
+ && !allowInsecureHttp
+ && !endpoint.IsLoopback)
+ {
+ throw new InvalidOperationException($"Control-plane endpoint from '{source}' must use HTTPS unless insecure HTTP is explicitly enabled or the endpoint is loopback.");
+ }
+
+ return endpoint;
+ }
+}
+
+internal sealed class MagicSettingsControlPlane : IMagicSettingsControlPlane where TSettings : class, new()
+{
+ private readonly MagicSettingsOptions _options;
+ private readonly MagicSettingsRuntime _runtime;
+ private readonly IMagicNodeAuthenticator _authenticator;
+ private readonly IMagicNodeIdentityManager _identityManager;
+ private readonly IMagicControlPlaneTransport _transport;
+ private readonly IMagicControlPlaneEndpointResolver _resolver;
+ private readonly MagicSettingsSchemaManifest _manifest;
+ private readonly SemaphoreSlim _gate = new(1, 1);
+ private MagicControlPlaneTrust? _trust;
+ private Uri? _runtimeOverrideEndpoint;
+ private MagicSettingsMigrationReport? _pendingMigrationReport;
+ private MagicIdentityContinuityProof? _pendingContinuityProof;
+ private long _lastRevision;
+
+ public MagicSettingsControlPlane(
+ MagicSettingsOptions options,
+ MagicSettingsRuntime runtime,
+ IMagicNodeAuthenticator authenticator,
+ IMagicNodeIdentityManager identityManager,
+ IMagicControlPlaneTransport transport,
+ IMagicControlPlaneEndpointResolver resolver,
+ JsonObject persistentDocument,
+ MagicSettingsMigrationReport? migrationReport)
+ {
+ _options = options;
+ _runtime = runtime;
+ _authenticator = authenticator;
+ _identityManager = identityManager;
+ _transport = transport;
+ _resolver = resolver;
+ _manifest = MagicManifestBuilder.Build(options);
+ _pendingMigrationReport = migrationReport;
+ CurrentEndpoint = resolver.Resolve(options, persistentDocument);
+ State = CurrentEndpoint.Endpoint is null ? MagicControlPlaneState.Disabled : MagicControlPlaneState.Configured;
+ _identityManager.IdentityChanged += OnIdentityChanged;
+ }
+
+ public MagicControlPlaneState State { get; private set; }
+ public MagicResolvedControlPlaneEndpoint CurrentEndpoint { get; private set; }
+
+ internal bool TryGetActiveConnection(out Uri endpoint, out MagicControlPlaneTrust trust)
+ {
+ if (State == MagicControlPlaneState.Active && CurrentEndpoint.Endpoint is { } current && _trust is { } currentTrust)
+ {
+ endpoint = current;
+ trust = currentTrust;
+ return true;
+ }
+
+ endpoint = null!;
+ trust = null!;
+ return false;
+ }
+
+ public async ValueTask BootstrapAsync(CancellationToken cancellationToken)
+ {
+ if (!_options.ControlPlane.Bootstrap.ConnectOnStartup || CurrentEndpoint.Endpoint is null)
+ {
+ return;
+ }
+
+ var trust = _options.ControlPlane.Bootstrap.Trust
+ ?? throw new InvalidOperationException("ConnectOnStartup requires an explicit MagicControlPlaneTrust configuration.");
+ await ConfigureResolvedAsync(CurrentEndpoint, trust, cancellationToken);
+ }
+
+ public async ValueTask ConfigureAsync(Uri endpoint, MagicControlPlaneTrust trust, CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(endpoint);
+ ArgumentNullException.ThrowIfNull(trust);
+ endpoint = MagicControlPlaneEndpointResolver.Validate(endpoint, "runtime override", _options.ControlPlane.Bootstrap.AllowInsecureHttp);
+ var resolved = new MagicResolvedControlPlaneEndpoint(endpoint, MagicControlPlaneEndpointSource.RuntimeOverride);
+ await ConfigureResolvedAsync(resolved, trust, cancellationToken);
+ _runtimeOverrideEndpoint = endpoint;
+ }
+
+ public async ValueTask DisconnectAsync(bool clearRemoteOverrides = false, CancellationToken cancellationToken = default)
+ {
+ await _gate.WaitAsync(cancellationToken);
+ try
+ {
+ State = MagicControlPlaneState.Disconnected;
+ _trust = null;
+ if (clearRemoteOverrides)
+ {
+ _lastRevision = 0;
+ _runtime.ClearRemote();
+ }
+ }
+ finally
+ {
+ _gate.Release();
+ }
+ }
+
+ public async ValueTask RefreshAsync(CancellationToken cancellationToken = default)
+ {
+ await _gate.WaitAsync(cancellationToken);
+ try
+ {
+ if (CurrentEndpoint.Endpoint is null || _trust is null)
+ {
+ return;
+ }
+
+ await SynchronizeUnderLockAsync(CurrentEndpoint, _trust, cancellationToken);
+ }
+ finally
+ {
+ _gate.Release();
+ }
+ }
+
+ public async ValueTask ReevaluatePersistentEndpointAsync(JsonObject persistentDocument, CancellationToken cancellationToken)
+ {
+ if (!_options.ControlPlane.Bootstrap.WatchPersistentEndpoint)
+ {
+ return;
+ }
+
+ var resolved = _resolver.Resolve(_options, persistentDocument, _runtimeOverrideEndpoint);
+ if (Equals(resolved.Endpoint, CurrentEndpoint.Endpoint))
+ {
+ return;
+ }
+
+ if (resolved.Endpoint is null)
+ {
+ await _gate.WaitAsync(cancellationToken);
+ try
+ {
+ CurrentEndpoint = MagicResolvedControlPlaneEndpoint.None;
+ _trust = null;
+ State = MagicControlPlaneState.Disabled;
+ if (!_options.ControlPlane.KeepLastKnownGoodDuringOutage)
+ {
+ _lastRevision = 0;
+ _runtime.ClearRemote();
+ }
+ }
+ finally
+ {
+ _gate.Release();
+ }
+ return;
+ }
+
+ var trust = _options.ControlPlane.Bootstrap.Trust;
+ if (trust is null)
+ {
+ CurrentEndpoint = resolved;
+ State = MagicControlPlaneState.Configured;
+ return;
+ }
+
+ await ConfigureResolvedAsync(resolved, trust, cancellationToken);
+ }
+
+ private async ValueTask ConfigureResolvedAsync(MagicResolvedControlPlaneEndpoint resolved, MagicControlPlaneTrust trust, CancellationToken cancellationToken)
+ {
+ await _gate.WaitAsync(cancellationToken);
+ try
+ {
+ var previousEndpoint = CurrentEndpoint;
+ var previousTrust = _trust;
+ State = MagicControlPlaneState.Connecting;
+ try
+ {
+ await SynchronizeUnderLockAsync(resolved, trust, cancellationToken);
+ CurrentEndpoint = resolved;
+ _trust = trust;
+ }
+ catch
+ {
+ CurrentEndpoint = previousEndpoint;
+ _trust = previousTrust;
+ State = MagicControlPlaneState.Faulted;
+ throw;
+ }
+ }
+ finally
+ {
+ _gate.Release();
+ }
+ }
+
+ private async ValueTask SynchronizeUnderLockAsync(MagicResolvedControlPlaneEndpoint resolved, MagicControlPlaneTrust trust, CancellationToken cancellationToken)
+ {
+ var endpoint = resolved.Endpoint ?? throw new InvalidOperationException("A control-plane endpoint is required.");
+ var syncUri = new Uri(endpoint, "magicsettings/sync");
+ var identity = await _authenticator.GetCurrentIdentityAsync(cancellationToken);
+ var signedPayloadHash = MagicSettingsSyncProof.ComputeBodySha256(
+ identity,
+ _manifest,
+ _lastRevision,
+ _pendingMigrationReport,
+ _pendingContinuityProof);
+ var proof = await _authenticator.CreateProofAsync(
+ new(trust.AuthorityId, "POST", syncUri, signedPayloadHash),
+ cancellationToken);
+ var response = await _transport.SynchronizeAsync(
+ endpoint,
+ trust,
+ new(identity, proof, _manifest, _lastRevision, _pendingMigrationReport, _pendingContinuityProof),
+ cancellationToken);
+
+ State = response.State;
+ if (response.State == MagicControlPlaneState.Active)
+ {
+ _runtime.ApplyRemoteSnapshot(response.Snapshot, DateTimeOffset.UtcNow);
+ _lastRevision = response.Snapshot.Revision;
+ _pendingMigrationReport = null;
+ _pendingContinuityProof = null;
+ }
+ }
+
+ private void OnIdentityChanged(object? sender, MagicIdentityChange change)
+ {
+ _lastRevision = 0;
+ _pendingContinuityProof = change.ContinuityProof;
+ if (change.Kind == MagicIdentityChangeKind.Reset || change.Kind == MagicIdentityChangeKind.RecoveredAfterLoss)
+ {
+ _runtime.ClearRemote();
+ }
+ State = MagicControlPlaneState.PendingApproval;
+ }
+}
+
+internal static class MagicManifestBuilder
+{
+ public static MagicSettingsSchemaManifest Build(MagicSettingsOptions options) where TSettings : class, new()
+ {
+ var discovered = new List();
+ Visit(typeof(TSettings), string.Empty, discovered, new HashSet());
+ var entries = discovered
+ .Select(item => item with
+ {
+ Sensitive = item.Sensitive || options.SensitivePaths.Contains(item.Path),
+ RemoteOverrideAllowed = item.RemoteOverrideAllowed && !string.Equals(
+ item.Path,
+ options.ControlPlane.Bootstrap.PersistentSettingPath,
+ StringComparison.OrdinalIgnoreCase)
+ })
+ .ToList();
+ var canonical = string.Join("\n", entries.OrderBy(static item => item.Path, StringComparer.Ordinal).Select(static item => $"{item.Path}|{item.Type}|{item.Nullable}|{item.Sensitive}|{item.RemoteOverrideAllowed}"));
+ var fingerprint = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(canonical))).ToLowerInvariant();
+ return new(options.ApplicationId, options.ApplicationVersion, options.SchemaVersion, fingerprint, entries);
+ }
+
+ private static void Visit(Type type, string path, ICollection entries, ISet stack)
+ {
+ if (!stack.Add(type))
+ {
+ return;
+ }
+
+ try
+ {
+ foreach (var property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(static property => property.CanRead))
+ {
+ var propertyPath = string.IsNullOrEmpty(path) ? property.Name : $"{path}:{property.Name}";
+ var propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
+ var sensitive = property.IsDefined(typeof(MagicSensitiveAttribute), inherit: true);
+ var remoteAllowed = property.GetCustomAttribute(inherit: true)?.Allowed ?? true;
+ var description = property.GetCustomAttribute(inherit: true)?.Description;
+ if (IsLeaf(propertyType))
+ {
+ entries.Add(new(
+ propertyPath,
+ propertyType.FullName ?? propertyType.Name,
+ Nullable.GetUnderlyingType(property.PropertyType) is not null || !property.PropertyType.IsValueType,
+ sensitive,
+ remoteAllowed,
+ description));
+ }
+ else if (!typeof(System.Collections.IEnumerable).IsAssignableFrom(propertyType) || propertyType == typeof(string))
+ {
+ Visit(propertyType, propertyPath, entries, stack);
+ }
+ else
+ {
+ entries.Add(new(propertyPath, propertyType.FullName ?? propertyType.Name, true, sensitive, remoteAllowed, description));
+ }
+ }
+ }
+ finally
+ {
+ stack.Remove(type);
+ }
+ }
+
+ private static bool IsLeaf(Type type)
+ => type.IsPrimitive || type.IsEnum || type == typeof(string) || type == typeof(decimal) || type == typeof(Guid) || type == typeof(Uri) || type == typeof(DateTime) || type == typeof(DateTimeOffset) || type == typeof(TimeSpan);
+}
+}
diff --git a/MagicSettings/MagicSettings/Source.03.cs b/MagicSettings/MagicSettings/Source.03.cs
new file mode 100644
index 0000000..4751f52
--- /dev/null
+++ b/MagicSettings/MagicSettings/Source.03.cs
@@ -0,0 +1,345 @@
+// Consolidated source file. See repository history and wiki for subsystem boundaries.
+using System.Text.Json.Nodes;
+using MagicSettings.Share;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.Json;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http.Headers;
+using System.Collections.Concurrent;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+using System.Collections;
+using System.Globalization;
+using System.ComponentModel.DataAnnotations;
+
+namespace MagicSettings
+{
+internal sealed record MagicSettingsDocumentResult(JsonObject Document, MagicSettingsMigrationReport? MigrationReport, bool Changed);
+
+internal static class MagicSettingsDocumentStore
+{
+ private const string MetadataProperty = "$magicSettings";
+
+ public static async ValueTask LoadAndReconcileAsync(
+ string path,
+ MagicSettingsOptions options,
+ bool forceRegenerate,
+ CancellationToken cancellationToken) where TSettings : class, new()
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(path) ?? AppContext.BaseDirectory);
+ var template = JsonSerializer.SerializeToNode(options.Template, options.Json) as JsonObject
+ ?? throw new InvalidOperationException("The settings template must serialize as a JSON object.");
+
+ JsonObject document;
+ var existed = File.Exists(path);
+ if (!existed || forceRegenerate)
+ {
+ document = (JsonObject)template.DeepClone();
+ }
+ else
+ {
+ await using var stream = File.OpenRead(path);
+ document = await JsonNode.ParseAsync(stream, cancellationToken: cancellationToken) as JsonObject
+ ?? throw new InvalidOperationException($"Settings file '{path}' must contain a JSON object.");
+ }
+
+ var currentVersion = GetSchemaVersion(document);
+ MagicSettingsMigrationReport? report = null;
+ if (existed && !forceRegenerate && currentVersion < options.SchemaVersion)
+ {
+ report = MagicMigrationRunner.Run(document, currentVersion, options.SchemaVersion, options.Migrations);
+ }
+
+ var strict = options.Failures.StrictDevelopmentMode && IsDevelopment(options.Environment);
+ var changed = !existed || forceRegenerate;
+ changed |= ReconcileObject(document, template, string.Empty, options, strict);
+ changed |= WriteMetadata(document, options);
+
+ if (changed)
+ {
+ await WriteAtomicAsync(path, document, options.Json, cancellationToken);
+ }
+
+ return new(document, report, changed);
+ }
+
+ public static async ValueTask ReadAsync(string path, CancellationToken cancellationToken)
+ {
+ await using var stream = File.OpenRead(path);
+ return await JsonNode.ParseAsync(stream, cancellationToken: cancellationToken) as JsonObject
+ ?? throw new InvalidOperationException($"Settings file '{path}' must contain a JSON object.");
+ }
+
+ private static bool ReconcileObject(JsonObject existing, JsonObject template, string path, MagicSettingsOptions options, bool strict)
+ where TSettings : class, new()
+ {
+ var changed = false;
+ foreach (var pair in template)
+ {
+ var childPath = string.IsNullOrEmpty(path) ? pair.Key : $"{path}:{pair.Key}";
+ if (!MagicJsonPath.TryGetProperty(existing, pair.Key, out var actualPropertyName, out var existingValue))
+ {
+ existing[pair.Key] = pair.Value?.DeepClone();
+ changed = true;
+ continue;
+ }
+
+ if (existingValue is JsonObject existingObject && pair.Value is JsonObject templateObject)
+ {
+ changed |= ReconcileObject(existingObject, templateObject, childPath, options, strict);
+ continue;
+ }
+
+ if (existingValue is JsonArray existingArray && pair.Value is JsonArray templateArray && !JsonNode.DeepEquals(existingArray, templateArray))
+ {
+ if (!options.ArrayPolicies.TryGetValue(childPath, out var policy))
+ {
+ if (strict)
+ {
+ throw new InvalidOperationException($"Array reconciliation for '{childPath}' is ambiguous. Configure an explicit MagicArrayMergePolicy.");
+ }
+
+ policy = MagicArrayMergePolicy.PreserveExisting;
+ }
+
+ changed |= ApplyArrayPolicy(existing, actualPropertyName ?? pair.Key, existingArray, templateArray, policy);
+ }
+ }
+
+ return changed;
+ }
+
+ private static bool ApplyArrayPolicy(JsonObject parent, string propertyName, JsonArray existing, JsonArray template, MagicArrayMergePolicy policy)
+ {
+ switch (policy)
+ {
+ case MagicArrayMergePolicy.PreserveExisting:
+ return false;
+ case MagicArrayMergePolicy.ReplaceWithTemplate:
+ parent[propertyName] = template.DeepClone();
+ return true;
+ case MagicArrayMergePolicy.AppendMissing:
+ case MagicArrayMergePolicy.Union:
+ var changed = false;
+ foreach (var item in template)
+ {
+ if (existing.Any(candidate => JsonNode.DeepEquals(candidate, item)))
+ {
+ continue;
+ }
+
+ existing.Add(item?.DeepClone());
+ changed = true;
+ }
+
+ return changed;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(policy), policy, null);
+ }
+ }
+
+ private static int GetSchemaVersion(JsonObject document)
+ => document[MetadataProperty]?["schemaVersion"]?.GetValue() ?? 1;
+
+ private static bool WriteMetadata(JsonObject document, MagicSettingsOptions options) where TSettings : class, new()
+ {
+ var metadata = document[MetadataProperty] as JsonObject ?? new JsonObject();
+ var before = metadata.ToJsonString();
+ metadata["schemaVersion"] = options.SchemaVersion;
+ metadata["applicationId"] = options.ApplicationId;
+ metadata["generatedBy"] = "MagicSettings";
+ document[MetadataProperty] = metadata;
+ return !string.Equals(before, metadata.ToJsonString(), StringComparison.Ordinal);
+ }
+
+ private static async ValueTask WriteAtomicAsync(string path, JsonObject document, JsonSerializerOptions options, CancellationToken cancellationToken)
+ {
+ var temporary = $"{path}.{Guid.NewGuid():N}.tmp";
+ await File.WriteAllTextAsync(temporary, document.ToJsonString(options), cancellationToken);
+ if (File.Exists(path))
+ {
+ var backup = $"{path}.bak";
+ File.Copy(path, backup, overwrite: true);
+ File.Move(temporary, path, overwrite: true);
+ }
+ else
+ {
+ File.Move(temporary, path);
+ }
+ }
+
+ private static bool IsDevelopment(string environment)
+ => environment.Equals("Development", StringComparison.OrdinalIgnoreCase)
+ || environment.Equals("Local", StringComparison.OrdinalIgnoreCase)
+ || environment.Equals("Test", StringComparison.OrdinalIgnoreCase);
+}
+}
+namespace MagicSettings
+{
+internal sealed class MagicSettingsHostedService : BackgroundService where TSettings : class, new()
+{
+ private readonly MagicSettingsOptions _options;
+ private readonly MagicSettingsRuntime _runtime;
+ private readonly MagicSettingsControlPlane _controlPlane;
+ private readonly ILogger> _logger;
+ private readonly string _settingsPath;
+ private DateTime _lastWriteUtc;
+ private DateTimeOffset _nextRemotePoll;
+
+ public MagicSettingsHostedService(
+ MagicSettingsOptions options,
+ MagicSettingsRuntime runtime,
+ MagicSettingsControlPlane controlPlane,
+ ILogger> logger,
+ string settingsPath)
+ {
+ _options = options;
+ _runtime = runtime;
+ _controlPlane = controlPlane;
+ _logger = logger;
+ _settingsPath = settingsPath;
+ _lastWriteUtc = File.Exists(settingsPath) ? File.GetLastWriteTimeUtc(settingsPath) : DateTime.MinValue;
+ _nextRemotePoll = DateTimeOffset.UtcNow.Add(options.ControlPlane.PollInterval);
+ }
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ try
+ {
+ await _controlPlane.BootstrapAsync(stoppingToken);
+ }
+ catch (Exception exception)
+ {
+ _logger.LogError(exception, "MagicSettings could not establish the bootstrap control-plane connection. Local configuration remains active.");
+ }
+
+ using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(500));
+ while (await timer.WaitForNextTickAsync(stoppingToken))
+ {
+ _runtime.RefreshRemoteExpirations(DateTimeOffset.UtcNow);
+ if (_options.ReloadOnChange)
+ {
+ await CheckPersistentFileAsync(stoppingToken);
+ }
+
+ if (DateTimeOffset.UtcNow >= _nextRemotePoll)
+ {
+ await PollRemoteAsync(stoppingToken);
+ _nextRemotePoll = DateTimeOffset.UtcNow.Add(JitteredPollInterval());
+ }
+ }
+ }
+
+ private async ValueTask CheckPersistentFileAsync(CancellationToken cancellationToken)
+ {
+ if (!File.Exists(_settingsPath))
+ {
+ return;
+ }
+
+ var currentWrite = File.GetLastWriteTimeUtc(_settingsPath);
+ if (currentWrite <= _lastWriteUtc)
+ {
+ return;
+ }
+
+ await Task.Delay(_options.ReloadDebounce, cancellationToken);
+ try
+ {
+ await _runtime.ReloadPersistentAsync(cancellationToken);
+ var document = await MagicSettingsDocumentStore.ReadAsync(_settingsPath, cancellationToken);
+ await _controlPlane.ReevaluatePersistentEndpointAsync(document, cancellationToken);
+ _lastWriteUtc = currentWrite;
+ }
+ catch (Exception exception)
+ {
+ _logger.LogError(exception, "MagicSettings rejected an invalid runtime reload and retained the last known good snapshot.");
+ }
+ }
+
+ private async ValueTask PollRemoteAsync(CancellationToken cancellationToken)
+ {
+ try
+ {
+ await _controlPlane.RefreshAsync(cancellationToken);
+ }
+ catch (Exception exception)
+ {
+ _logger.LogWarning(exception, "MagicSettings could not refresh remote overrides. The last known good remote snapshot remains active.");
+ }
+ }
+
+ private TimeSpan JitteredPollInterval()
+ {
+ var jitter = _options.ControlPlane.PollJitter;
+ if (jitter <= TimeSpan.Zero)
+ {
+ return _options.ControlPlane.PollInterval;
+ }
+
+ var milliseconds = Random.Shared.NextDouble() * jitter.TotalMilliseconds * 2 - jitter.TotalMilliseconds;
+ return _options.ControlPlane.PollInterval + TimeSpan.FromMilliseconds(milliseconds);
+ }
+}
+}
+namespace MagicSettings
+{
+public sealed class MagicNodeAuthenticationHandler : DelegatingHandler
+{
+ private readonly IMagicNodeAuthenticator _authenticator;
+ private readonly string _audience;
+
+ public MagicNodeAuthenticationHandler(IMagicNodeAuthenticator authenticator, string audience)
+ {
+ _authenticator = authenticator;
+ _audience = audience;
+ }
+
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(request.RequestUri);
+ var body = request.Content is null
+ ? Array.Empty()
+ : await request.Content.ReadAsByteArrayAsync(cancellationToken);
+
+ if (request.Content is not null)
+ {
+ var replacement = new ByteArrayContent(body);
+ foreach (var header in request.Content.Headers)
+ {
+ replacement.Headers.TryAddWithoutValidation(header.Key, header.Value);
+ }
+ request.Content = replacement;
+ }
+
+ var proof = await _authenticator.CreateProofAsync(
+ new(
+ _audience,
+ request.Method.Method,
+ request.RequestUri,
+ MagicHash.Sha256Hex(body)),
+ cancellationToken);
+
+ request.Headers.Authorization = new AuthenticationHeaderValue("MagicNode", MagicNodeProofCodec.Encode(proof));
+ request.Headers.TryAddWithoutValidation("X-Magic-Node-Id", proof.NodeId.ToString("D"));
+ request.Headers.TryAddWithoutValidation("X-Magic-Credential-Id", proof.CredentialId.ToString("D"));
+ return await base.SendAsync(request, cancellationToken);
+ }
+}
+
+public static class MagicHttpClientBuilderExtensions
+{
+ public static IHttpClientBuilder AddMagicNodeAuthentication(this IHttpClientBuilder builder, string audience)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(audience);
+ return builder.AddHttpMessageHandler(serviceProvider =>
+ new MagicNodeAuthenticationHandler(serviceProvider.GetRequiredService(), audience));
+ }
+}
+}
diff --git a/MagicSettings/MagicSettings/Source.04.cs b/MagicSettings/MagicSettings/Source.04.cs
new file mode 100644
index 0000000..897ebf7
--- /dev/null
+++ b/MagicSettings/MagicSettings/Source.04.cs
@@ -0,0 +1,423 @@
+// Consolidated source file. See repository history and wiki for subsystem boundaries.
+using System.Text.Json.Nodes;
+using MagicSettings.Share;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.Json;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http.Headers;
+using System.Collections.Concurrent;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+using System.Collections;
+using System.Globalization;
+using System.ComponentModel.DataAnnotations;
+
+namespace MagicSettings
+{
+public sealed class HttpMagicControlPlaneTransport : IMagicControlPlaneTransport, IMagicSecretTransport, IDisposable
+{
+ private readonly HttpClient _systemTlsClient;
+ private readonly ConcurrentDictionary _pinnedClients = new(StringComparer.OrdinalIgnoreCase);
+ private readonly JsonSerializerOptions _json;
+
+ public HttpMagicControlPlaneTransport(TimeSpan? timeout = null, JsonSerializerOptions? json = null)
+ {
+ _systemTlsClient = CreateClient(certificateValidator: null, timeout);
+ _json = json ?? new JsonSerializerOptions(JsonSerializerDefaults.Web);
+ }
+
+ public async ValueTask SynchronizeAsync(
+ Uri endpoint,
+ MagicControlPlaneTrust trust,
+ MagicSettingsSyncRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(endpoint);
+ ArgumentNullException.ThrowIfNull(trust);
+ ArgumentNullException.ThrowIfNull(request);
+
+ var client = SelectClient(trust);
+
+ var syncUri = new Uri(endpoint, "magicsettings/sync");
+ using var message = new HttpRequestMessage(HttpMethod.Post, syncUri)
+ {
+ Content = new StringContent(JsonSerializer.Serialize(request, _json), Encoding.UTF8, "application/json")
+ };
+ message.Headers.Authorization = new AuthenticationHeaderValue("MagicNode", MagicNodeProofCodec.Encode(request.Proof));
+ using var response = await client.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
+ response.EnsureSuccessStatusCode();
+ await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
+ return await JsonSerializer.DeserializeAsync(stream, _json, cancellationToken)
+ ?? throw new InvalidOperationException("The control plane returned an empty synchronization response.");
+ }
+
+
+ public async ValueTask ResolveSecretAsync(
+ Uri endpoint,
+ MagicControlPlaneTrust trust,
+ MagicSecretRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(endpoint);
+ ArgumentNullException.ThrowIfNull(trust);
+ ArgumentNullException.ThrowIfNull(request);
+
+ var client = SelectClient(trust);
+ var requestUri = new Uri(endpoint, "magicsettings/secrets/resolve");
+ using var message = new HttpRequestMessage(HttpMethod.Post, requestUri)
+ {
+ Content = new StringContent(JsonSerializer.Serialize(request, _json), Encoding.UTF8, "application/json")
+ };
+ message.Headers.Authorization = new AuthenticationHeaderValue("MagicNode", MagicNodeProofCodec.Encode(request.Proof));
+ using var response = await client.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
+ response.EnsureSuccessStatusCode();
+ await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
+ return await JsonSerializer.DeserializeAsync(stream, _json, cancellationToken)
+ ?? throw new InvalidOperationException("The control plane returned an empty secret response.");
+ }
+
+ public void Dispose()
+ {
+ _systemTlsClient.Dispose();
+ foreach (var client in _pinnedClients.Values)
+ {
+ client.Dispose();
+ }
+ }
+
+ private HttpClient SelectClient(MagicControlPlaneTrust trust)
+ => trust.Mode switch
+ {
+ MagicControlPlaneTrustMode.SystemTls => _systemTlsClient,
+ MagicControlPlaneTrustMode.PinnedPublicKey => GetPinnedClient(trust),
+ _ => throw new ArgumentOutOfRangeException(nameof(trust), trust.Mode, "Unsupported control-plane trust mode.")
+ };
+
+ private HttpClient GetPinnedClient(MagicControlPlaneTrust trust)
+ {
+ var expected = NormalizeFingerprint(trust.PinnedPublicKeyFingerprint
+ ?? throw new InvalidOperationException("PinnedPublicKey trust requires a public-key fingerprint."));
+ return _pinnedClients.GetOrAdd(expected, fingerprint => CreateClient(
+ (_, certificate, _, _) => certificate is not null && FixedTimeEquals(PublicKeyFingerprint(certificate), fingerprint),
+ _systemTlsClient.Timeout));
+ }
+
+ private static HttpClient CreateClient(
+ Func? certificateValidator,
+ TimeSpan? timeout)
+ {
+ var handler = new HttpClientHandler
+ {
+ AllowAutoRedirect = false
+ };
+ if (certificateValidator is not null)
+ {
+ handler.ServerCertificateCustomValidationCallback = certificateValidator;
+ }
+
+ return new HttpClient(handler, disposeHandler: true)
+ {
+ Timeout = timeout ?? TimeSpan.FromSeconds(30)
+ };
+ }
+
+ private static string PublicKeyFingerprint(X509Certificate2 certificate)
+ => Convert.ToHexString(SHA256.HashData(certificate.GetPublicKey())).ToLowerInvariant();
+
+ private static string NormalizeFingerprint(string value)
+ => value.Replace(":", string.Empty, StringComparison.Ordinal).Trim().ToLowerInvariant();
+
+ private static bool FixedTimeEquals(string actual, string expected)
+ {
+ var actualBytes = Encoding.ASCII.GetBytes(actual);
+ var expectedBytes = Encoding.ASCII.GetBytes(expected);
+ return actualBytes.Length == expectedBytes.Length
+ && CryptographicOperations.FixedTimeEquals(actualBytes, expectedBytes);
+ }
+}
+}
+namespace MagicSettings
+{
+public sealed class FileMagicNodeIdentityStore : IMagicNodeIdentityStore
+{
+ private readonly string _path;
+ private readonly JsonSerializerOptions _json = new(JsonSerializerDefaults.Web) { WriteIndented = true };
+
+ public FileMagicNodeIdentityStore(string path) => _path = Path.GetFullPath(path);
+
+ public async ValueTask LoadAsync(CancellationToken cancellationToken = default)
+ {
+ if (!File.Exists(_path))
+ {
+ return null;
+ }
+
+ await using var stream = File.OpenRead(_path);
+ return await JsonSerializer.DeserializeAsync(stream, _json, cancellationToken);
+ }
+
+ public async ValueTask SaveAsync(MagicStoredNodeIdentity identity, CancellationToken cancellationToken = default)
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(_path) ?? AppContext.BaseDirectory);
+ var temporary = $"{_path}.{Guid.NewGuid():N}.tmp";
+ await using (var stream = File.Create(temporary))
+ {
+ await JsonSerializer.SerializeAsync(stream, identity, _json, cancellationToken);
+ await stream.FlushAsync(cancellationToken);
+ }
+
+ TryRestrictPermissions(temporary);
+ File.Move(temporary, _path, overwrite: true);
+ TryRestrictPermissions(_path);
+ }
+
+ public ValueTask DeleteAsync(CancellationToken cancellationToken = default)
+ {
+ if (File.Exists(_path))
+ {
+ File.Delete(_path);
+ }
+
+ return ValueTask.CompletedTask;
+ }
+
+ private static void TryRestrictPermissions(string path)
+ {
+ if (!OperatingSystem.IsWindows())
+ {
+ File.SetUnixFileMode(path, UnixFileMode.UserRead | UnixFileMode.UserWrite);
+ }
+ }
+}
+
+internal static class MagicIdentityCryptography
+{
+ public const string ProofVersion = "MAGICSETTINGS-PROOF-V1";
+ public const string SignatureAlgorithm = "ECDSA_P256_SHA256_P1363";
+
+ public static MagicStoredNodeIdentity Create(Guid? nodeId = null)
+ {
+ using var key = ECDsa.Create(ECCurve.NamedCurves.nistP256);
+ return new(
+ nodeId ?? Guid.NewGuid(),
+ Guid.NewGuid(),
+ Convert.ToBase64String(key.ExportSubjectPublicKeyInfo()),
+ Convert.ToBase64String(key.ExportPkcs8PrivateKey()),
+ DateTimeOffset.UtcNow);
+ }
+
+ public static MagicNodeIdentityDescriptor Describe(MagicStoredNodeIdentity identity)
+ => new(
+ identity.NodeId,
+ identity.CredentialId,
+ MagicCredentialKind.EcdsaP256,
+ SignatureAlgorithm,
+ identity.PublicKey,
+ Fingerprint(identity.PublicKey),
+ identity.CreatedUtc);
+
+ public static string Sign(MagicStoredNodeIdentity identity, string canonical)
+ {
+ using var key = ECDsa.Create();
+ key.ImportPkcs8PrivateKey(Convert.FromBase64String(identity.PrivateKey), out _);
+ var signature = key.SignData(
+ Encoding.UTF8.GetBytes(canonical),
+ HashAlgorithmName.SHA256,
+ DSASignatureFormat.IeeeP1363FixedFieldConcatenation);
+ return Convert.ToBase64String(signature);
+ }
+
+ public static bool Verify(string publicKey, string canonical, string signature)
+ {
+ using var key = ECDsa.Create();
+ key.ImportSubjectPublicKeyInfo(Convert.FromBase64String(publicKey), out _);
+ return key.VerifyData(
+ Encoding.UTF8.GetBytes(canonical),
+ Convert.FromBase64String(signature),
+ HashAlgorithmName.SHA256,
+ DSASignatureFormat.IeeeP1363FixedFieldConcatenation);
+ }
+
+ public static string Canonicalize(MagicAuthenticationProof proof)
+ => string.Join('\n',
+ proof.Version,
+ proof.NodeId.ToString("D"),
+ proof.CredentialId.ToString("D"),
+ proof.Audience,
+ proof.Method.ToUpperInvariant(),
+ proof.Target,
+ proof.BodySha256.ToLowerInvariant(),
+ proof.IssuedUtc.ToUnixTimeMilliseconds().ToString(System.Globalization.CultureInfo.InvariantCulture),
+ proof.ExpiresUtc.ToUnixTimeMilliseconds().ToString(System.Globalization.CultureInfo.InvariantCulture),
+ proof.Nonce);
+
+ public static string CanonicalizeContinuity(MagicNodeIdentityDescriptor previous, MagicNodeIdentityDescriptor current, DateTimeOffset issuedUtc, string nonce)
+ => string.Join('\n',
+ "MAGICSETTINGS-IDENTITY-CONTINUITY-V1",
+ previous.NodeId.ToString("D"),
+ previous.CredentialId.ToString("D"),
+ current.CredentialId.ToString("D"),
+ current.PublicKey,
+ issuedUtc.ToUnixTimeMilliseconds().ToString(System.Globalization.CultureInfo.InvariantCulture),
+ nonce);
+
+ public static string NormalizeTarget(Uri uri)
+ => uri.IsAbsoluteUri
+ ? uri.GetComponents(UriComponents.SchemeAndServer | UriComponents.PathAndQuery, UriFormat.UriEscaped)
+ : uri.OriginalString;
+
+ public static string Fingerprint(string publicKey)
+ => Convert.ToHexString(SHA256.HashData(Convert.FromBase64String(publicKey))).ToLowerInvariant();
+}
+
+public sealed class MagicNodeIdentityManager : IMagicNodeIdentityManager, IMagicNodeAuthenticator
+{
+ private readonly IMagicNodeIdentityStore _store;
+ private readonly SemaphoreSlim _gate = new(1, 1);
+ private MagicStoredNodeIdentity? _current;
+
+ public MagicNodeIdentityManager(IMagicNodeIdentityStore store) => _store = store;
+
+ public event EventHandler? IdentityChanged;
+
+ public async ValueTask GetCurrentAsync(CancellationToken cancellationToken = default)
+ => MagicIdentityCryptography.Describe(await GetStoredAsync(cancellationToken));
+
+ public ValueTask GetCurrentIdentityAsync(CancellationToken cancellationToken = default)
+ => GetCurrentAsync(cancellationToken);
+
+ public async ValueTask CreateProofAsync(MagicAuthenticationRequest request, CancellationToken cancellationToken = default)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(request.Audience);
+ ArgumentException.ThrowIfNullOrWhiteSpace(request.Method);
+ ArgumentNullException.ThrowIfNull(request.Uri);
+ ArgumentException.ThrowIfNullOrWhiteSpace(request.BodySha256);
+
+ var identity = await GetStoredAsync(cancellationToken);
+ var issued = DateTimeOffset.UtcNow;
+ var validFor = request.ValidFor ?? TimeSpan.FromMinutes(1);
+ if (validFor <= TimeSpan.Zero || validFor > TimeSpan.FromMinutes(5))
+ {
+ throw new ArgumentOutOfRangeException(nameof(request), "Authentication proofs must be valid for more than zero and no longer than five minutes.");
+ }
+
+ var unsigned = new MagicAuthenticationProof(
+ MagicIdentityCryptography.ProofVersion,
+ identity.NodeId,
+ identity.CredentialId,
+ request.Audience,
+ request.Method.ToUpperInvariant(),
+ MagicIdentityCryptography.NormalizeTarget(request.Uri),
+ request.BodySha256.ToLowerInvariant(),
+ issued,
+ issued.Add(validFor),
+ MagicNodeProofCodec.Base64UrlEncode(RandomNumberGenerator.GetBytes(24)),
+ string.Empty);
+
+ return unsigned with { Signature = MagicIdentityCryptography.Sign(identity, MagicIdentityCryptography.Canonicalize(unsigned)) };
+ }
+
+ public async ValueTask RotateAsync(string reason, CancellationToken cancellationToken = default)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(reason);
+ await _gate.WaitAsync(cancellationToken);
+ try
+ {
+ var previous = await LoadOrCreateUnderLockAsync(cancellationToken);
+ var next = MagicIdentityCryptography.Create(previous.NodeId);
+ var previousDescriptor = MagicIdentityCryptography.Describe(previous);
+ var nextDescriptor = MagicIdentityCryptography.Describe(next);
+ var issued = DateTimeOffset.UtcNow;
+ var nonce = MagicNodeProofCodec.Base64UrlEncode(RandomNumberGenerator.GetBytes(24));
+ var canonical = MagicIdentityCryptography.CanonicalizeContinuity(previousDescriptor, nextDescriptor, issued, nonce);
+ var continuity = new MagicIdentityContinuityProof(previousDescriptor, nextDescriptor, issued, nonce, MagicIdentityCryptography.Sign(previous, canonical));
+ await _store.SaveAsync(next, cancellationToken);
+ _current = next;
+ var change = new MagicIdentityChange(MagicIdentityChangeKind.Rotated, nextDescriptor, previousDescriptor, continuity, reason);
+ IdentityChanged?.Invoke(this, change);
+ return change;
+ }
+ finally
+ {
+ _gate.Release();
+ }
+ }
+
+ public async ValueTask ResetAsync(MagicIdentityResetRequest request, CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(request);
+ if (!request.ConfirmDestructiveReset)
+ {
+ throw new InvalidOperationException("Identity reset is destructive and requires ConfirmDestructiveReset=true.");
+ }
+
+ await _gate.WaitAsync(cancellationToken);
+ try
+ {
+ var previous = await _store.LoadAsync(cancellationToken);
+ var next = MagicIdentityCryptography.Create();
+ await _store.SaveAsync(next, cancellationToken);
+ _current = next;
+ var change = new MagicIdentityChange(
+ MagicIdentityChangeKind.Reset,
+ MagicIdentityCryptography.Describe(next),
+ previous is null ? null : MagicIdentityCryptography.Describe(previous),
+ null,
+ request.Reason);
+ IdentityChanged?.Invoke(this, change);
+ return change;
+ }
+ finally
+ {
+ _gate.Release();
+ }
+ }
+
+ private async ValueTask GetStoredAsync(CancellationToken cancellationToken)
+ {
+ if (_current is not null)
+ {
+ return _current;
+ }
+
+ await _gate.WaitAsync(cancellationToken);
+ try
+ {
+ return await LoadOrCreateUnderLockAsync(cancellationToken);
+ }
+ finally
+ {
+ _gate.Release();
+ }
+ }
+
+ private async ValueTask LoadOrCreateUnderLockAsync(CancellationToken cancellationToken)
+ {
+ if (_current is not null)
+ {
+ return _current;
+ }
+
+ _current = await _store.LoadAsync(cancellationToken);
+ if (_current is null)
+ {
+ _current = MagicIdentityCryptography.Create();
+ await _store.SaveAsync(_current, cancellationToken);
+ }
+
+ return _current;
+ }
+}
+
+public static class MagicHash
+{
+ public static string Sha256Hex(ReadOnlySpan value) => Convert.ToHexString(SHA256.HashData(value)).ToLowerInvariant();
+ public static string EmptySha256 { get; } = Sha256Hex(Array.Empty());
+}
+}
diff --git a/MagicSettings/MagicSettings/Source.05.cs b/MagicSettings/MagicSettings/Source.05.cs
new file mode 100644
index 0000000..de23aef
--- /dev/null
+++ b/MagicSettings/MagicSettings/Source.05.cs
@@ -0,0 +1,414 @@
+// Consolidated source file. See repository history and wiki for subsystem boundaries.
+using System.Text.Json.Nodes;
+using MagicSettings.Share;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.Json;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http.Headers;
+using System.Collections.Concurrent;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+using System.Collections;
+using System.Globalization;
+using System.ComponentModel.DataAnnotations;
+
+namespace MagicSettings
+{
+public static class MagicSettingsInitializationExtensions
+{
+ public static async ValueTask AddMagicSettingsAsync(
+ this IHostApplicationBuilder builder,
+ string[]? args = null,
+ Action>? configure = null,
+ CancellationToken cancellationToken = default)
+ where TSettings : class, new()
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ var options = new MagicSettingsOptions();
+ configure?.Invoke(options);
+ options.Environment = MagicSettingsEnvironmentResolver.Resolve(options.Environment, builder.Environment.EnvironmentName);
+ if (options.EnvironmentProfiles.TryGetValue(options.Environment, out var environmentProfile))
+ {
+ environmentProfile(options.Template);
+ }
+
+ var command = MagicSettingsCommandLine.Parse(args ?? Array.Empty());
+ var path = MagicSettingsPathResolver.Resolve(options);
+ var documentResult = await MagicSettingsDocumentStore.LoadAndReconcileAsync(
+ path,
+ options,
+ command.ForceGenerate,
+ cancellationToken);
+
+ if (command.PrintPath)
+ {
+ Console.Out.WriteLine(path);
+ }
+
+ var identityPath = MagicIdentityPathResolver.Resolve(path, options.IdentityPath, options.IdentityFileName);
+ var identityStore = options.IdentityStore ?? new FileMagicNodeIdentityStore(identityPath);
+ var identityManager = new MagicNodeIdentityManager(identityStore);
+ await identityManager.GetCurrentAsync(cancellationToken);
+
+ var configurationProvider = new MagicSettingsConfigurationProvider();
+ var runtime = new MagicSettingsRuntime(options, configurationProvider, path);
+ await runtime.InitializeAsync(documentResult.Document, cancellationToken);
+
+ var endpointResolver = options.ControlPlaneEndpointResolver ?? new MagicControlPlaneEndpointResolver();
+ var transport = options.ControlPlaneTransport ?? new HttpMagicControlPlaneTransport();
+ var controlPlane = new MagicSettingsControlPlane(
+ options,
+ runtime,
+ identityManager,
+ identityManager,
+ transport,
+ endpointResolver,
+ documentResult.Document,
+ documentResult.MigrationReport);
+
+ builder.Configuration.Add(new MagicSettingsConfigurationSource(configurationProvider));
+ builder.Services.AddSingleton(options);
+ builder.Services.AddSingleton>(runtime);
+ builder.Services.AddSingleton(runtime);
+ builder.Services.AddSingleton(identityManager);
+ builder.Services.AddSingleton(identityManager);
+ builder.Services.AddSingleton(controlPlane);
+ builder.Services.AddSingleton(controlPlane);
+ builder.Services.AddSingleton(endpointResolver);
+ builder.Services.AddSingleton(transport);
+ if (transport is IMagicSecretTransport secretTransport)
+ {
+ builder.Services.AddSingleton(secretTransport);
+ builder.Services.AddSingleton(
+ new MagicControlPlaneSecretProvider(controlPlane, identityManager, secretTransport, options));
+ }
+ builder.Services.AddSingleton(new MagicSettingsRuntimeRegistration(path));
+ builder.Services.AddHostedService(serviceProvider =>
+ new MagicSettingsHostedService(
+ options,
+ runtime,
+ controlPlane,
+ serviceProvider.GetRequiredService>>(),
+ path));
+ builder.Services.AddOptions().Bind(builder.Configuration);
+
+ var shouldExit = command.GenerateOnly || command.ValidateOnly || command.PrintPath;
+ return new(shouldExit, 0, path, options.Environment);
+ }
+}
+
+public sealed record MagicSettingsRuntimeRegistration(string SettingsPath);
+
+internal sealed record MagicSettingsCommandLine(bool GenerateOnly, bool ForceGenerate, bool ValidateOnly, bool PrintPath)
+{
+ public static MagicSettingsCommandLine Parse(IEnumerable args)
+ {
+ var set = args.ToHashSet(StringComparer.OrdinalIgnoreCase);
+ var force = set.Contains("--magic-settings-force-generate");
+ return new(
+ set.Contains("--magic-settings-generate") || force,
+ force,
+ set.Contains("--magic-settings-validate"),
+ set.Contains("--magic-settings-print-path"));
+ }
+}
+}
+namespace MagicSettings
+{
+internal static class MagicJsonPath
+{
+ public static bool TryGet(JsonObject root, string path, out JsonNode? value)
+ {
+ value = root;
+ foreach (var segment in Split(path))
+ {
+ if (value is not JsonObject obj || !TryGetProperty(obj, segment, out _, out value))
+ {
+ value = null;
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static void Set(JsonObject root, string path, JsonNode? value)
+ {
+ var segments = Split(path);
+ if (segments.Length == 0)
+ {
+ throw new ArgumentException("A setting path is required.", nameof(path));
+ }
+
+ var current = root;
+ for (var index = 0; index < segments.Length - 1; index++)
+ {
+ var segment = segments[index];
+ if (!TryGetProperty(current, segment, out var actualName, out var existing) || existing is not JsonObject child)
+ {
+ child = new JsonObject();
+ current[actualName ?? segment] = child;
+ }
+
+ current = child;
+ }
+
+ if (TryGetProperty(current, segments[^1], out var finalName, out _))
+ {
+ current[finalName!] = value?.DeepClone();
+ }
+ else
+ {
+ current[segments[^1]] = value?.DeepClone();
+ }
+ }
+
+ public static bool TryTake(JsonObject root, string path, out JsonNode? value)
+ {
+ value = null;
+ var segments = Split(path);
+ if (segments.Length == 0)
+ {
+ return false;
+ }
+
+ var current = root;
+ for (var index = 0; index < segments.Length - 1; index++)
+ {
+ if (!TryGetProperty(current, segments[index], out _, out var existing) || existing is not JsonObject child)
+ {
+ return false;
+ }
+
+ current = child;
+ }
+
+ if (!TryGetProperty(current, segments[^1], out var actualName, out value))
+ {
+ return false;
+ }
+
+ current.Remove(actualName!);
+ return true;
+ }
+
+ public static bool TryGetProperty(JsonObject obj, string name, out string? actualName, out JsonNode? value)
+ {
+ if (obj.TryGetPropertyValue(name, out value))
+ {
+ actualName = name;
+ return true;
+ }
+
+ foreach (var pair in obj)
+ {
+ if (string.Equals(pair.Key, name, StringComparison.OrdinalIgnoreCase))
+ {
+ actualName = pair.Key;
+ value = pair.Value;
+ return true;
+ }
+ }
+
+ actualName = null;
+ value = null;
+ return false;
+ }
+
+ public static string[] Split(string path)
+ => path.Split(new[] { ':', '.' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+}
+
+internal static class MagicJsonFlattener
+{
+ public static Dictionary Flatten(JsonObject root)
+ {
+ var result = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ Visit(root, null, result);
+ return result;
+ }
+
+ private static void Visit(JsonNode? node, string? path, IDictionary result)
+ {
+ switch (node)
+ {
+ case JsonObject obj:
+ foreach (var pair in obj)
+ {
+ if (pair.Key == "$magicSettings")
+ {
+ continue;
+ }
+
+ Visit(pair.Value, path is null ? pair.Key : $"{path}:{pair.Key}", result);
+ }
+ break;
+ case JsonArray array:
+ for (var index = 0; index < array.Count; index++)
+ {
+ Visit(array[index], $"{path}:{index}", result);
+ }
+ break;
+ case JsonValue value when path is not null:
+ result[path] = ToConfigurationString(value);
+ break;
+ case null when path is not null:
+ result[path] = null;
+ break;
+ }
+ }
+
+ private static string? ToConfigurationString(JsonValue value)
+ {
+ if (value.TryGetValue(out var text)) return text;
+ if (value.TryGetValue(out var boolean)) return boolean ? "true" : "false";
+ if (value.TryGetValue(out var integer)) return integer.ToString(CultureInfo.InvariantCulture);
+ if (value.TryGetValue(out var longInteger)) return longInteger.ToString(CultureInfo.InvariantCulture);
+ if (value.TryGetValue(out var number)) return number.ToString(CultureInfo.InvariantCulture);
+ if (value.TryGetValue(out var decimalNumber)) return decimalNumber.ToString(CultureInfo.InvariantCulture);
+ return value.ToJsonString();
+ }
+}
+
+internal static class MagicEnvironmentOverrides
+{
+ public static Dictionary Read(string prefix)
+ {
+ var result = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables())
+ {
+ if (entry.Key is not string name || !name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ var path = name[prefix.Length..].Replace("__", ":", StringComparison.Ordinal);
+ result[path] = entry.Value?.ToString();
+ }
+
+ return result;
+ }
+}
+}
+namespace MagicSettings
+{
+public sealed class MagicMigrationContext
+{
+ private readonly List _safeOperations = new();
+ private readonly List _reviewItems = new();
+
+ public IReadOnlyList SafeOperations => _safeOperations;
+ public IReadOnlyList ReviewItems => _reviewItems;
+
+ public void RecordSafe(string operation) => _safeOperations.Add(operation);
+
+ public void RequireReview(string path, string operation, string reason, string? proposedPath = null, MagicMigrationReviewSeverity severity = MagicMigrationReviewSeverity.Warning)
+ => _reviewItems.Add(new(path, operation, severity, reason, proposedPath));
+
+ public void Rename(JsonObject document, string from, string to, bool remoteSafeProjection = true)
+ {
+ if (!MagicJsonPath.TryTake(document, from, out var value))
+ {
+ return;
+ }
+
+ if (MagicJsonPath.TryGet(document, to, out _))
+ {
+ throw new InvalidOperationException($"Cannot rename '{from}' to '{to}' because the destination already exists.");
+ }
+
+ MagicJsonPath.Set(document, to, value);
+ RecordSafe($"rename:{from}->{to}");
+ if (!remoteSafeProjection)
+ {
+ RequireReview(from, "rename", "The remote record should retain the original value until reviewed.", to);
+ }
+ }
+
+ public void Transform(
+ JsonObject document,
+ string path,
+ Func transform,
+ string description,
+ bool remoteSafeProjection = false)
+ {
+ ArgumentNullException.ThrowIfNull(transform);
+ if (!MagicJsonPath.TryGet(document, path, out var current))
+ {
+ return;
+ }
+
+ MagicJsonPath.Set(document, path, transform(current?.DeepClone()));
+ RecordSafe($"transform:{path}:{description}");
+ if (!remoteSafeProjection)
+ {
+ RequireReview(path, "transform", description);
+ }
+ }
+
+ public void SetIfMissing(JsonObject document, string path, JsonNode? value)
+ {
+ if (MagicJsonPath.TryGet(document, path, out _))
+ {
+ return;
+ }
+
+ MagicJsonPath.Set(document, path, value);
+ RecordSafe($"set-if-missing:{path}");
+ }
+
+ public void Remove(JsonObject document, string path, string reason)
+ {
+ if (MagicJsonPath.TryTake(document, path, out _))
+ {
+ RecordSafe($"local-remove:{path}");
+ RequireReview(path, "remove", reason, severity: MagicMigrationReviewSeverity.Destructive);
+ }
+ }
+}
+
+internal static class MagicMigrationRunner
+{
+ public static MagicSettingsMigrationReport? Run(JsonObject document, int currentVersion, int targetVersion, IEnumerable migrations)
+ {
+ if (currentVersion > targetVersion)
+ {
+ throw new InvalidOperationException($"Downgrading settings from schema {currentVersion} to {targetVersion} is not supported.");
+ }
+
+ if (currentVersion == targetVersion)
+ {
+ return null;
+ }
+
+ var ordered = migrations.OrderBy(static migration => migration.FromVersion).ToList();
+ var context = new MagicMigrationContext();
+ var version = currentVersion;
+ while (version < targetVersion)
+ {
+ var migration = ordered.SingleOrDefault(item => item.FromVersion == version)
+ ?? throw new InvalidOperationException($"No migration exists from schema version {version}.");
+ if (migration.ToVersion <= version)
+ {
+ throw new InvalidOperationException($"Migration {migration.GetType().Name} does not advance the schema version.");
+ }
+
+ migration.Apply(document, context);
+ version = migration.ToVersion;
+ }
+
+ if (version != targetVersion)
+ {
+ throw new InvalidOperationException($"Migration chain ended at schema version {version}, expected {targetVersion}.");
+ }
+
+ return new(currentVersion, targetVersion, context.SafeOperations.ToArray(), context.ReviewItems.ToArray());
+ }
+}
+}
diff --git a/MagicSettings/MagicSettings/Source.06.cs b/MagicSettings/MagicSettings/Source.06.cs
new file mode 100644
index 0000000..295fd69
--- /dev/null
+++ b/MagicSettings/MagicSettings/Source.06.cs
@@ -0,0 +1,160 @@
+// Consolidated source file. See repository history and wiki for subsystem boundaries.
+using System.Text.Json.Nodes;
+using MagicSettings.Share;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.Json;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http.Headers;
+using System.Collections.Concurrent;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+using System.Collections;
+using System.Globalization;
+using System.ComponentModel.DataAnnotations;
+
+namespace MagicSettings
+{
+public enum MagicFailureAction
+{
+ StopStartup,
+ WarnAndContinue,
+ KeepLastKnownGood
+}
+
+public enum MagicArrayMergePolicy
+{
+ PreserveExisting,
+ ReplaceWithTemplate,
+ AppendMissing,
+ Union
+}
+
+public sealed class MagicSettingsFailurePolicy
+{
+ public bool StrictDevelopmentMode { get; set; } = true;
+ public MagicFailureAction AmbiguousArrayInProduction { get; set; } = MagicFailureAction.WarnAndContinue;
+ public MagicFailureAction RuntimeReloadFailure { get; set; } = MagicFailureAction.KeepLastKnownGood;
+}
+
+public sealed class MagicControlPlaneBootstrapOptions
+{
+ public string EnvironmentVariableName { get; set; } = "MAGICSETTINGS_CONTROL_PLANE_ENDPOINT";
+ public string PersistentSettingPath { get; set; } = "MagicSettings:ControlPlane:Endpoint";
+ public Uri? CodeFallbackEndpoint { get; set; }
+ public MagicControlPlaneTrust? Trust { get; set; }
+ public bool ConnectOnStartup { get; set; }
+ public bool WatchPersistentEndpoint { get; set; } = true;
+ public bool AllowInsecureHttp { get; set; }
+}
+
+public sealed class MagicControlPlaneOptions
+{
+ public MagicControlPlaneBootstrapOptions Bootstrap { get; } = new();
+ public TimeSpan PollInterval { get; set; } = TimeSpan.FromSeconds(90);
+ public TimeSpan PollJitter { get; set; } = TimeSpan.FromSeconds(30);
+ public bool KeepLastKnownGoodDuringOutage { get; set; } = true;
+}
+
+public sealed class MagicSettingsOptions where TSettings : class, new()
+{
+ public string ApplicationId { get; set; } = typeof(TSettings).Assembly.GetName().Name ?? typeof(TSettings).Name;
+ public string ApplicationVersion { get; set; } = typeof(TSettings).Assembly.GetName().Version?.ToString() ?? "0.0.0";
+ public int SchemaVersion { get; set; } = 1;
+ public TSettings Template { get; set; } = new();
+ public string? Path { get; set; }
+ public string FileName { get; set; } = "appsettings.json";
+ public string EnvironmentOverridePrefix { get; set; } = "MagicSettings__";
+ public string Environment { get; set; } = string.Empty;
+ public bool ReloadOnChange { get; set; } = true;
+ public TimeSpan ReloadDebounce { get; set; } = TimeSpan.FromMilliseconds(350);
+ public bool PreserveUnknownProperties { get; set; } = true;
+ public string IdentityFileName { get; set; } = ".magicsettings.identity.json";
+ public string? IdentityPath { get; set; }
+ public JsonSerializerOptions Json { get; } = new() { WriteIndented = true, PropertyNameCaseInsensitive = true };
+ public MagicSettingsFailurePolicy Failures { get; } = new();
+ public MagicControlPlaneOptions ControlPlane { get; } = new();
+ public IDictionary> EnvironmentProfiles { get; } = new Dictionary>(StringComparer.OrdinalIgnoreCase);
+ public IList Migrations { get; } = new List();
+ public IList> Validators { get; } = new List>();
+ public IList Providers { get; } = new List();
+ public IDictionary ArrayPolicies { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ public ISet SensitivePaths { get; } = new HashSet(StringComparer.OrdinalIgnoreCase);
+ public IMagicControlPlaneTransport? ControlPlaneTransport { get; set; }
+ public IMagicControlPlaneEndpointResolver? ControlPlaneEndpointResolver { get; set; }
+ public IMagicNodeIdentityStore? IdentityStore { get; set; }
+
+ public void ConfigureEnvironment(string environment, Action configure)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(environment);
+ ArgumentNullException.ThrowIfNull(configure);
+ EnvironmentProfiles[environment] = configure;
+ }
+}
+
+public sealed record MagicSettingsInitializationResult(bool ShouldExit, int ExitCode, string SettingsPath, string Environment);
+}
+namespace MagicSettings
+{
+public static class MagicSettingsEnvironmentResolver
+{
+ public static string Resolve(string? explicitEnvironment = null, string? hostEnvironment = null)
+ => FirstNonEmpty(
+ explicitEnvironment,
+ Environment.GetEnvironmentVariable("MAGICSETTINGS_ENVIRONMENT"),
+ Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT"),
+ Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"),
+ hostEnvironment,
+ "Production")!;
+
+ private static string? FirstNonEmpty(params string?[] values)
+ => values.FirstOrDefault(static value => !string.IsNullOrWhiteSpace(value));
+}
+
+public static class MagicSettingsPathResolver
+{
+ public static string Resolve(MagicSettingsOptions options) where TSettings : class, new()
+ {
+ var configured = options.Path;
+ if (string.IsNullOrWhiteSpace(configured))
+ {
+ configured = Environment.GetEnvironmentVariable("MAGICSETTINGS_PATH");
+ }
+
+ if (string.IsNullOrWhiteSpace(configured))
+ {
+ return Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, options.FileName));
+ }
+
+ var fullPath = Path.GetFullPath(configured);
+ if (Path.HasExtension(fullPath) && string.Equals(Path.GetExtension(fullPath), ".json", StringComparison.OrdinalIgnoreCase))
+ {
+ return fullPath;
+ }
+
+ return Path.Combine(fullPath, options.FileName);
+ }
+}
+
+
+public static class MagicIdentityPathResolver
+{
+ public static string Resolve(string settingsPath, string? configuredPath, string fileName)
+ {
+ var configured = string.IsNullOrWhiteSpace(configuredPath)
+ ? Environment.GetEnvironmentVariable("MAGICSETTINGS_IDENTITY_PATH")
+ : configuredPath;
+ if (string.IsNullOrWhiteSpace(configured))
+ {
+ return Path.GetFullPath(Path.Combine(Path.GetDirectoryName(settingsPath) ?? AppContext.BaseDirectory, fileName));
+ }
+
+ var fullPath = Path.GetFullPath(configured);
+ return Path.HasExtension(fullPath) ? fullPath : Path.Combine(fullPath, fileName);
+ }
+}
diff --git a/MagicSettings/MagicSettings/Source.07.cs b/MagicSettings/MagicSettings/Source.07.cs
new file mode 100644
index 0000000..ba79bd8
--- /dev/null
+++ b/MagicSettings/MagicSettings/Source.07.cs
@@ -0,0 +1,405 @@
+// Consolidated source file. See repository history and wiki for subsystem boundaries.
+using System.Text.Json.Nodes;
+using MagicSettings.Share;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.Json;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http.Headers;
+using System.Collections.Concurrent;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+using System.Collections;
+using System.Globalization;
+using System.ComponentModel.DataAnnotations;
+
+namespace MagicSettings
+{
+internal sealed class MagicSettingsConfigurationSource : IConfigurationSource
+{
+ private readonly MagicSettingsConfigurationProvider _provider;
+ public MagicSettingsConfigurationSource(MagicSettingsConfigurationProvider provider) => _provider = provider;
+ public IConfigurationProvider Build(IConfigurationBuilder builder) => _provider;
+}
+
+internal sealed class MagicSettingsConfigurationProvider : ConfigurationProvider
+{
+ public void Publish(IReadOnlyDictionary values)
+ {
+ Data = new Dictionary(values, StringComparer.OrdinalIgnoreCase);
+ OnReload();
+ }
+}
+
+internal sealed class MagicSettingsRuntime : IMagicSettings where TSettings : class, new()
+{
+ private readonly object _gate = new();
+ private readonly MagicSettingsOptions _options;
+ private readonly MagicSettingsConfigurationProvider _configurationProvider;
+ private readonly string _settingsPath;
+ private Dictionary _persistent = new(StringComparer.OrdinalIgnoreCase);
+ private Dictionary _environment = new(StringComparer.OrdinalIgnoreCase);
+ private Dictionary _remote = new(StringComparer.OrdinalIgnoreCase);
+ private MagicRemoteSnapshot _remoteSnapshot = MagicRemoteSnapshot.Empty;
+ private Dictionary _providers = new(StringComparer.OrdinalIgnoreCase);
+ private Dictionary _effective = new(StringComparer.OrdinalIgnoreCase);
+ private TSettings _current = new();
+ private long _revision;
+
+ public MagicSettingsRuntime(MagicSettingsOptions options, MagicSettingsConfigurationProvider configurationProvider, string settingsPath)
+ {
+ _options = options;
+ _configurationProvider = configurationProvider;
+ _settingsPath = settingsPath;
+ }
+
+ public TSettings Current { get { lock (_gate) return _current; } }
+ public long Revision { get { lock (_gate) return _revision; } }
+ public event EventHandler>? Changed;
+
+ public async ValueTask InitializeAsync(JsonObject document, CancellationToken cancellationToken)
+ {
+ var persistent = MagicJsonFlattener.Flatten(document);
+ var environment = MagicEnvironmentOverrides.Read(_options.EnvironmentOverridePrefix);
+ var providers = await LoadCustomProvidersAsync(cancellationToken);
+ PublishCandidate(persistent: persistent, environment: environment, providers: providers);
+ }
+
+ public async ValueTask ReloadPersistentAsync(CancellationToken cancellationToken)
+ {
+ var document = await MagicSettingsDocumentStore.ReadAsync(_settingsPath, cancellationToken);
+ var persistent = MagicJsonFlattener.Flatten(document);
+ var providers = await LoadCustomProvidersAsync(cancellationToken);
+ PublishCandidate(persistent: persistent, providers: providers);
+ }
+
+ public void RefreshEnvironment()
+ => PublishCandidate(environment: MagicEnvironmentOverrides.Read(_options.EnvironmentOverridePrefix));
+
+ public void ApplyRemoteSnapshot(MagicRemoteSnapshot snapshot, DateTimeOffset nowUtc)
+ {
+ ArgumentNullException.ThrowIfNull(snapshot);
+ var values = MaterializeRemote(snapshot, nowUtc);
+ PublishCandidate(remote: values, remoteSnapshot: snapshot, updateRemoteSnapshot: true);
+ }
+
+ public void RefreshRemoteExpirations(DateTimeOffset nowUtc)
+ {
+ MagicRemoteSnapshot snapshot;
+ Dictionary current;
+ lock (_gate)
+ {
+ snapshot = _remoteSnapshot;
+ current = _remote;
+ }
+
+ var values = MaterializeRemote(snapshot, nowUtc);
+ if (!DictionaryEquals(current, values))
+ {
+ PublishCandidate(remote: values);
+ }
+ }
+
+ public void ClearRemote()
+ {
+ PublishCandidate(
+ remote: new Dictionary(StringComparer.OrdinalIgnoreCase),
+ remoteSnapshot: MagicRemoteSnapshot.Empty,
+ updateRemoteSnapshot: true);
+ }
+
+ public MagicSettingExplanation Explain(string path)
+ {
+ lock (_gate)
+ {
+ var sensitive = _options.SensitivePaths.Contains(path);
+ var sources = new[]
+ {
+ SourceValue("Remote", _remote, path, sensitive),
+ SourceValue("Environment", _environment, path, sensitive),
+ SourceValue("CustomProviders", _providers, path, sensitive),
+ SourceValue("PersistentFile", _persistent, path, sensitive)
+ };
+ _effective.TryGetValue(path, out var effective);
+ var source = sources.FirstOrDefault(static item => item.Present)?.Source ?? "Missing";
+ return new(path, sensitive && effective is not null ? "[REDACTED]" : effective, source, sources, sensitive);
+ }
+ }
+
+ private async ValueTask> LoadCustomProvidersAsync(CancellationToken cancellationToken)
+ {
+ var result = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ foreach (var provider in _options.Providers.OrderBy(static item => item.Priority))
+ {
+ var values = await provider.LoadAsync(cancellationToken);
+ foreach (var pair in values)
+ {
+ result[pair.Key] = pair.Value;
+ }
+ }
+
+ return result;
+ }
+
+ private void PublishCandidate(
+ Dictionary? persistent = null,
+ Dictionary? providers = null,
+ Dictionary? environment = null,
+ Dictionary? remote = null,
+ MagicRemoteSnapshot? remoteSnapshot = null,
+ bool updateRemoteSnapshot = false)
+ {
+ TSettings previous;
+ TSettings current;
+ long revision;
+ Dictionary effective;
+ lock (_gate)
+ {
+ var nextPersistent = persistent ?? _persistent;
+ var nextProviders = providers ?? _providers;
+ var nextEnvironment = environment ?? _environment;
+ var nextRemote = remote ?? _remote;
+
+ effective = new Dictionary(nextPersistent, StringComparer.OrdinalIgnoreCase);
+ Merge(effective, nextProviders);
+ Merge(effective, nextEnvironment);
+ Merge(effective, nextRemote);
+
+ var builder = new ConfigurationBuilder().AddInMemoryCollection(effective);
+ current = builder.Build().Get() ?? new TSettings();
+ Validate(current);
+
+ previous = _current;
+ _persistent = nextPersistent;
+ _providers = nextProviders;
+ _environment = nextEnvironment;
+ _remote = nextRemote;
+ if (updateRemoteSnapshot)
+ {
+ _remoteSnapshot = remoteSnapshot ?? MagicRemoteSnapshot.Empty;
+ }
+ _current = current;
+ _effective = effective;
+ revision = ++_revision;
+ }
+
+ _configurationProvider.Publish(effective);
+ Changed?.Invoke(this, new(previous, current, revision));
+ }
+
+ private void Validate(TSettings settings)
+ {
+ var dataAnnotationFailures = new List();
+ ValidateDataAnnotations(settings, string.Empty, dataAnnotationFailures, new HashSet