From 88cdbe03023a44e36b0dcd5f933305a231e54b62 Mon Sep 17 00:00:00 2001 From: James Waugh Date: Tue, 29 Sep 2026 16:21:25 -0400 Subject: [PATCH 001/128] chore(staging): staging auth base URL --- bos.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bos.config.json b/bos.config.json index 0c6899e7..c11306a3 100644 --- a/bos.config.json +++ b/bos.config.json @@ -42,7 +42,7 @@ "auth": { "extends": "bos://auth.everything.near/auth.everything.dev#app.auth", "variables": { - "baseUrl": "https://multiagency.ai", + "baseUrl": "https://dev.multiagency.ai", "trustedOrigins": [ "http://localhost:3000" ], From 948ff91797d39cedd22fce70ce2c0a1be5a99a53 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 20:22:54 +0000 Subject: [PATCH 002/128] chore: update staging deployment URLs [skip ci] --- bos.config.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bos.config.json b/bos.config.json index c11306a3..d7b16bd2 100644 --- a/bos.config.json +++ b/bos.config.json @@ -13,9 +13,9 @@ "ui": { "name": "ui", "development": "local:ui", - "production": "https://multiagentic-166-ui-dashboard-multiagency-5c1f7a739-ze.zephyrcloud.app", + "production": "https://multiagentic-171-ui-dashboard-multiagency-be8a75d6b-ze.zephyrcloud.app", "integrity": "sha384-yQ8HQfipLh4u5nX5BaJf5DqQJZPF5YXzbsw4ciuW4Pxka2ulfsJXPe4JJlJWIfGi", - "ssr": "https://multiagentic-170-ui-dashboard-multiagency-3d5b7cd1b-ze.zephyrcloud.app", + "ssr": "https://multiagentic-175-ui-dashboard-multiagency-30d6edc76-ze.zephyrcloud.app", "ssrIntegrity": "sha384-0SsOOmxzQ1C2jVv6xCfw1tzY0g9mMnAvHMz+pRxDTeoXRTJBT9py4it4sWWsQH3c" }, "api": { @@ -36,7 +36,7 @@ "APP_ORIGIN", "FASTNEAR_API_KEY" ], - "production": "https://multiagentic-169-api-dashboard-multiagency-d406cc723-ze.zephyrcloud.app", + "production": "https://multiagentic-174-api-dashboard-multiagency-b90947cb4-ze.zephyrcloud.app", "integrity": "sha384-axWJzoJ5AN1MlXygXaYeX1oOr8tSrJJ9yjtLuAAq/siHMJrVr0FP1Nm5/3n5QrVr" }, "auth": { @@ -64,7 +64,7 @@ "secrets": [ "PROJECTS_DATABASE_URL" ], - "production": "https://multiagentic-167-everything-dev-projects-plugin-d-914b5aeb3-ze.zephyrcloud.app", + "production": "https://multiagentic-173-everything-dev-projects-plugin-d-11cfb397a-ze.zephyrcloud.app", "integrity": "sha384-dTbUDrOJ14Li9F/56M3JPp0LlCxq1bZTFfw9h3JYFXqd1bFtnQr3DHHWwnGRFqwl" }, "builders": { @@ -72,7 +72,7 @@ "secrets": [ "BUILDERS_DATABASE_URL" ], - "production": "https://multiagentic-168-everything-dev-builders-plugin-d-d85f828a4-ze.zephyrcloud.app", + "production": "https://multiagentic-172-everything-dev-builders-plugin-d-f256ced4b-ze.zephyrcloud.app", "integrity": "sha384-SxdK807mpmECvN0qkkDaV6yArqmy/6i14CKxmTg8fRDDyw0hWuEeut0ljzTITTK5" } } From 8ce677293d0ee8752e6cf1f8d0b9a136b83fe398 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 21:07:55 +0000 Subject: [PATCH 003/128] chore: update staging deployment URLs [skip ci] --- bos.config.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bos.config.json b/bos.config.json index d7b16bd2..8d21cd1e 100644 --- a/bos.config.json +++ b/bos.config.json @@ -13,9 +13,9 @@ "ui": { "name": "ui", "development": "local:ui", - "production": "https://multiagentic-171-ui-dashboard-multiagency-be8a75d6b-ze.zephyrcloud.app", + "production": "https://multiagentic-186-ui-dashboard-multiagency-e0a5c35fa-ze.zephyrcloud.app", "integrity": "sha384-yQ8HQfipLh4u5nX5BaJf5DqQJZPF5YXzbsw4ciuW4Pxka2ulfsJXPe4JJlJWIfGi", - "ssr": "https://multiagentic-175-ui-dashboard-multiagency-30d6edc76-ze.zephyrcloud.app", + "ssr": "https://multiagentic-190-ui-dashboard-multiagency-6e56cadde-ze.zephyrcloud.app", "ssrIntegrity": "sha384-0SsOOmxzQ1C2jVv6xCfw1tzY0g9mMnAvHMz+pRxDTeoXRTJBT9py4it4sWWsQH3c" }, "api": { @@ -36,7 +36,7 @@ "APP_ORIGIN", "FASTNEAR_API_KEY" ], - "production": "https://multiagentic-174-api-dashboard-multiagency-b90947cb4-ze.zephyrcloud.app", + "production": "https://multiagentic-189-api-dashboard-multiagency-6da154cf9-ze.zephyrcloud.app", "integrity": "sha384-axWJzoJ5AN1MlXygXaYeX1oOr8tSrJJ9yjtLuAAq/siHMJrVr0FP1Nm5/3n5QrVr" }, "auth": { @@ -64,7 +64,7 @@ "secrets": [ "PROJECTS_DATABASE_URL" ], - "production": "https://multiagentic-173-everything-dev-projects-plugin-d-11cfb397a-ze.zephyrcloud.app", + "production": "https://multiagentic-188-everything-dev-projects-plugin-d-083870f0e-ze.zephyrcloud.app", "integrity": "sha384-dTbUDrOJ14Li9F/56M3JPp0LlCxq1bZTFfw9h3JYFXqd1bFtnQr3DHHWwnGRFqwl" }, "builders": { @@ -72,7 +72,7 @@ "secrets": [ "BUILDERS_DATABASE_URL" ], - "production": "https://multiagentic-172-everything-dev-builders-plugin-d-f256ced4b-ze.zephyrcloud.app", + "production": "https://multiagentic-187-everything-dev-builders-plugin-d-337e9aa2e-ze.zephyrcloud.app", "integrity": "sha384-SxdK807mpmECvN0qkkDaV6yArqmy/6i14CKxmTg8fRDDyw0hWuEeut0ljzTITTK5" } } From 0adc7fba469bc8ba2ac96cf49d7e1c77d0436a3c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 23:22:26 +0000 Subject: [PATCH 004/128] chore: update staging deployment URLs [skip ci] --- bos.config.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/bos.config.json b/bos.config.json index 8d21cd1e..d6489842 100644 --- a/bos.config.json +++ b/bos.config.json @@ -13,10 +13,10 @@ "ui": { "name": "ui", "development": "local:ui", - "production": "https://multiagentic-186-ui-dashboard-multiagency-e0a5c35fa-ze.zephyrcloud.app", - "integrity": "sha384-yQ8HQfipLh4u5nX5BaJf5DqQJZPF5YXzbsw4ciuW4Pxka2ulfsJXPe4JJlJWIfGi", - "ssr": "https://multiagentic-190-ui-dashboard-multiagency-6e56cadde-ze.zephyrcloud.app", - "ssrIntegrity": "sha384-0SsOOmxzQ1C2jVv6xCfw1tzY0g9mMnAvHMz+pRxDTeoXRTJBT9py4it4sWWsQH3c" + "production": "https://multiagentic-201-ui-dashboard-multiagency-0c97efc94-ze.zephyrcloud.app", + "integrity": "sha384-s3IK2AaACgR0mbd2VgWcJiXgxEAQTc009+sNs7tJ69ui1wxr87rCptsC59hEku2m", + "ssr": "https://multiagentic-205-ui-dashboard-multiagency-5b078fe53-ze.zephyrcloud.app", + "ssrIntegrity": "sha384-jRKb8K68MRBQ3Vw53SRQNfUK2UpAeVr0/0nckOdRTJKr3+ps1H+5mqF3RXan0Ytn" }, "api": { "name": "api", @@ -36,8 +36,8 @@ "APP_ORIGIN", "FASTNEAR_API_KEY" ], - "production": "https://multiagentic-189-api-dashboard-multiagency-6da154cf9-ze.zephyrcloud.app", - "integrity": "sha384-axWJzoJ5AN1MlXygXaYeX1oOr8tSrJJ9yjtLuAAq/siHMJrVr0FP1Nm5/3n5QrVr" + "production": "https://multiagentic-204-api-dashboard-multiagency-6846a77be-ze.zephyrcloud.app", + "integrity": "sha384-NBoulHMiE+WeadkiRukVbwvhaEhLEHwUDWkVP7YH1nbaVroBLnOcK+rpV2cDjpt1" }, "auth": { "extends": "bos://auth.everything.near/auth.everything.dev#app.auth", @@ -64,16 +64,16 @@ "secrets": [ "PROJECTS_DATABASE_URL" ], - "production": "https://multiagentic-188-everything-dev-projects-plugin-d-083870f0e-ze.zephyrcloud.app", - "integrity": "sha384-dTbUDrOJ14Li9F/56M3JPp0LlCxq1bZTFfw9h3JYFXqd1bFtnQr3DHHWwnGRFqwl" + "production": "https://multiagentic-203-everything-dev-projects-plugin-d-f12e6429c-ze.zephyrcloud.app", + "integrity": "sha384-qPOhAJTs/T7AsUmqhbdUnrcWJc4R5zW8DtsCJLScSOOyQS46quiS1qmJ/PfVmlH8" }, "builders": { "development": "local:plugins/builders", "secrets": [ "BUILDERS_DATABASE_URL" ], - "production": "https://multiagentic-187-everything-dev-builders-plugin-d-337e9aa2e-ze.zephyrcloud.app", - "integrity": "sha384-SxdK807mpmECvN0qkkDaV6yArqmy/6i14CKxmTg8fRDDyw0hWuEeut0ljzTITTK5" + "production": "https://multiagentic-202-everything-dev-builders-plugin-d-59f9ae692-ze.zephyrcloud.app", + "integrity": "sha384-9Y9Z9Xt+7xuGxRxE7vLkkTOnoDRrql3qHYnjdyX4eBUqSJl+uFgWY3UK9bT11BLV" } } } From 5f22378bd7bd74f1b121d0aebee4abfa0c5f5cc0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 23:56:31 +0000 Subject: [PATCH 005/128] chore: update staging deployment URLs [skip ci] --- bos.config.json | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/bos.config.json b/bos.config.json index d6489842..dd0210aa 100644 --- a/bos.config.json +++ b/bos.config.json @@ -13,10 +13,10 @@ "ui": { "name": "ui", "development": "local:ui", - "production": "https://multiagentic-201-ui-dashboard-multiagency-0c97efc94-ze.zephyrcloud.app", - "integrity": "sha384-s3IK2AaACgR0mbd2VgWcJiXgxEAQTc009+sNs7tJ69ui1wxr87rCptsC59hEku2m", - "ssr": "https://multiagentic-205-ui-dashboard-multiagency-5b078fe53-ze.zephyrcloud.app", - "ssrIntegrity": "sha384-jRKb8K68MRBQ3Vw53SRQNfUK2UpAeVr0/0nckOdRTJKr3+ps1H+5mqF3RXan0Ytn" + "production": "https://multiagentic-206-ui-dashboard-multiagency-de1da1014-ze.zephyrcloud.app", + "integrity": "sha384-lpFNCayhhC9E/OMgdUQvKk2qap5fotJg5iN7tbGGzkeMt6OHuLXj2A2w+fuazA/u", + "ssr": "https://multiagentic-210-ui-dashboard-multiagency-bceb4141d-ze.zephyrcloud.app", + "ssrIntegrity": "sha384-b69WxhUYb3NmOpUCHvEUbGUrkAQUJU9muA9NB+5VH8H8TTjH8lla7tUgIIkc9yNV" }, "api": { "name": "api", @@ -36,8 +36,7 @@ "APP_ORIGIN", "FASTNEAR_API_KEY" ], - "production": "https://multiagentic-204-api-dashboard-multiagency-6846a77be-ze.zephyrcloud.app", - "integrity": "sha384-NBoulHMiE+WeadkiRukVbwvhaEhLEHwUDWkVP7YH1nbaVroBLnOcK+rpV2cDjpt1" + "production": "https://multiagentic-209-api-dashboard-multiagency-c979ae4fb-ze.zephyrcloud.app" }, "auth": { "extends": "bos://auth.everything.near/auth.everything.dev#app.auth", @@ -64,7 +63,7 @@ "secrets": [ "PROJECTS_DATABASE_URL" ], - "production": "https://multiagentic-203-everything-dev-projects-plugin-d-f12e6429c-ze.zephyrcloud.app", + "production": "https://multiagentic-208-everything-dev-projects-plugin-d-11213275c-ze.zephyrcloud.app", "integrity": "sha384-qPOhAJTs/T7AsUmqhbdUnrcWJc4R5zW8DtsCJLScSOOyQS46quiS1qmJ/PfVmlH8" }, "builders": { @@ -72,7 +71,7 @@ "secrets": [ "BUILDERS_DATABASE_URL" ], - "production": "https://multiagentic-202-everything-dev-builders-plugin-d-59f9ae692-ze.zephyrcloud.app", + "production": "https://multiagentic-207-everything-dev-builders-plugin-d-beaa73311-ze.zephyrcloud.app", "integrity": "sha384-9Y9Z9Xt+7xuGxRxE7vLkkTOnoDRrql3qHYnjdyX4eBUqSJl+uFgWY3UK9bT11BLV" } } From 46117b43b127064e15c78c4da94a8af8ed99a34b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 00:34:45 +0000 Subject: [PATCH 006/128] chore: update staging deployment URLs [skip ci] --- bos.config.json | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/bos.config.json b/bos.config.json index dd0210aa..cb26102c 100644 --- a/bos.config.json +++ b/bos.config.json @@ -13,10 +13,10 @@ "ui": { "name": "ui", "development": "local:ui", - "production": "https://multiagentic-206-ui-dashboard-multiagency-de1da1014-ze.zephyrcloud.app", - "integrity": "sha384-lpFNCayhhC9E/OMgdUQvKk2qap5fotJg5iN7tbGGzkeMt6OHuLXj2A2w+fuazA/u", - "ssr": "https://multiagentic-210-ui-dashboard-multiagency-bceb4141d-ze.zephyrcloud.app", - "ssrIntegrity": "sha384-b69WxhUYb3NmOpUCHvEUbGUrkAQUJU9muA9NB+5VH8H8TTjH8lla7tUgIIkc9yNV" + "production": "https://multiagentic-211-ui-dashboard-multiagency-40472462d-ze.zephyrcloud.app", + "integrity": "sha384-2N5afGTbX7ZjPoI/B/k6SN0CsZXLrH2qRGnvbQDUHu4l/HPlWd5qaY3sj4v/AIxP", + "ssr": "https://multiagentic-215-ui-dashboard-multiagency-4b616f9db-ze.zephyrcloud.app", + "ssrIntegrity": "sha384-T6F3MtqbcuYp0R7Le2GqzNEc0dt2WJvzoXkLuCU5Tr2oLceRV2k5tKelkqmQaG3O" }, "api": { "name": "api", @@ -36,7 +36,8 @@ "APP_ORIGIN", "FASTNEAR_API_KEY" ], - "production": "https://multiagentic-209-api-dashboard-multiagency-c979ae4fb-ze.zephyrcloud.app" + "production": "https://multiagentic-214-api-dashboard-multiagency-5e86ff63f-ze.zephyrcloud.app", + "integrity": "sha384-NBoulHMiE+WeadkiRukVbwvhaEhLEHwUDWkVP7YH1nbaVroBLnOcK+rpV2cDjpt1" }, "auth": { "extends": "bos://auth.everything.near/auth.everything.dev#app.auth", @@ -63,7 +64,7 @@ "secrets": [ "PROJECTS_DATABASE_URL" ], - "production": "https://multiagentic-208-everything-dev-projects-plugin-d-11213275c-ze.zephyrcloud.app", + "production": "https://multiagentic-212-everything-dev-projects-plugin-d-1fded0249-ze.zephyrcloud.app", "integrity": "sha384-qPOhAJTs/T7AsUmqhbdUnrcWJc4R5zW8DtsCJLScSOOyQS46quiS1qmJ/PfVmlH8" }, "builders": { @@ -71,7 +72,7 @@ "secrets": [ "BUILDERS_DATABASE_URL" ], - "production": "https://multiagentic-207-everything-dev-builders-plugin-d-beaa73311-ze.zephyrcloud.app", + "production": "https://multiagentic-213-everything-dev-builders-plugin-d-ed23b5f39-ze.zephyrcloud.app", "integrity": "sha384-9Y9Z9Xt+7xuGxRxE7vLkkTOnoDRrql3qHYnjdyX4eBUqSJl+uFgWY3UK9bT11BLV" } } From a0ae42da02ab2f035b0a0b2eaaaf5db2f5641d59 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 17:41:30 +0000 Subject: [PATCH 007/128] chore: update staging deployment URLs [skip ci] --- bos.config.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bos.config.json b/bos.config.json index cb26102c..6e838aca 100644 --- a/bos.config.json +++ b/bos.config.json @@ -13,10 +13,10 @@ "ui": { "name": "ui", "development": "local:ui", - "production": "https://multiagentic-211-ui-dashboard-multiagency-40472462d-ze.zephyrcloud.app", - "integrity": "sha384-2N5afGTbX7ZjPoI/B/k6SN0CsZXLrH2qRGnvbQDUHu4l/HPlWd5qaY3sj4v/AIxP", - "ssr": "https://multiagentic-215-ui-dashboard-multiagency-4b616f9db-ze.zephyrcloud.app", - "ssrIntegrity": "sha384-T6F3MtqbcuYp0R7Le2GqzNEc0dt2WJvzoXkLuCU5Tr2oLceRV2k5tKelkqmQaG3O" + "production": "https://multiagentic-226-ui-dashboard-multiagency-b6cc2852e-ze.zephyrcloud.app", + "integrity": "sha384-I2BAfE8AscFQ7w/K3PiobZVBkNrj9I8A5VDQTAppArZiO8w+AefZhjPJGQflHCUY", + "ssr": "https://multiagentic-230-ui-dashboard-multiagency-4ce88ce10-ze.zephyrcloud.app", + "ssrIntegrity": "sha384-r8jZRSsDBzYCLfvCeaJfxPd4DafZCgPMn9XWM4ZgJ1nVBYcyUBFd8XFugd6gxSYB" }, "api": { "name": "api", @@ -36,7 +36,7 @@ "APP_ORIGIN", "FASTNEAR_API_KEY" ], - "production": "https://multiagentic-214-api-dashboard-multiagency-5e86ff63f-ze.zephyrcloud.app", + "production": "https://multiagentic-229-api-dashboard-multiagency-7a9c91e37-ze.zephyrcloud.app", "integrity": "sha384-NBoulHMiE+WeadkiRukVbwvhaEhLEHwUDWkVP7YH1nbaVroBLnOcK+rpV2cDjpt1" }, "auth": { @@ -64,7 +64,7 @@ "secrets": [ "PROJECTS_DATABASE_URL" ], - "production": "https://multiagentic-212-everything-dev-projects-plugin-d-1fded0249-ze.zephyrcloud.app", + "production": "https://multiagentic-228-everything-dev-projects-plugin-d-0ad7a8762-ze.zephyrcloud.app", "integrity": "sha384-qPOhAJTs/T7AsUmqhbdUnrcWJc4R5zW8DtsCJLScSOOyQS46quiS1qmJ/PfVmlH8" }, "builders": { @@ -72,7 +72,7 @@ "secrets": [ "BUILDERS_DATABASE_URL" ], - "production": "https://multiagentic-213-everything-dev-builders-plugin-d-ed23b5f39-ze.zephyrcloud.app", + "production": "https://multiagentic-227-everything-dev-builders-plugin-d-14c59c3f8-ze.zephyrcloud.app", "integrity": "sha384-9Y9Z9Xt+7xuGxRxE7vLkkTOnoDRrql3qHYnjdyX4eBUqSJl+uFgWY3UK9bT11BLV" } } From b3282db24be024642f2cd44a61dc3d3bc93aee27 Mon Sep 17 00:00:00 2001 From: James Waugh Date: Wed, 30 Sep 2026 15:40:49 -0400 Subject: [PATCH 008/128] chore(staging): publish staging config under the dev.multiagency.ai registry key --- bos.config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bos.config.json b/bos.config.json index 6e838aca..5198dd24 100644 --- a/bos.config.json +++ b/bos.config.json @@ -1,12 +1,12 @@ { "extends": "bos://dev.everything.near/dev.everything.dev", "account": "multiagentic.near", - "domain": "dev.multiagency.ai", + "domain": "multiagency.ai", "title": "MultiAgency", "description": "Human-led, AI-native agencies for hire.", "testnet": "agency.testnet", "staging": { - "domain": "staging.multiagency.ai" + "domain": "dev.multiagency.ai" }, "repository": "https://github.com/MultiAgency/dashboard", "app": { From 5bd42c1261a102336adf29ef2669fba9b9073c35 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 19:42:27 +0000 Subject: [PATCH 009/128] chore: update staging deployment URLs [skip ci] --- bos.config.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bos.config.json b/bos.config.json index 5198dd24..777320d2 100644 --- a/bos.config.json +++ b/bos.config.json @@ -13,9 +13,9 @@ "ui": { "name": "ui", "development": "local:ui", - "production": "https://multiagentic-226-ui-dashboard-multiagency-b6cc2852e-ze.zephyrcloud.app", + "production": "https://multiagentic-241-ui-dashboard-multiagency-840f2efdd-ze.zephyrcloud.app", "integrity": "sha384-I2BAfE8AscFQ7w/K3PiobZVBkNrj9I8A5VDQTAppArZiO8w+AefZhjPJGQflHCUY", - "ssr": "https://multiagentic-230-ui-dashboard-multiagency-4ce88ce10-ze.zephyrcloud.app", + "ssr": "https://multiagentic-245-ui-dashboard-multiagency-08ff18b06-ze.zephyrcloud.app", "ssrIntegrity": "sha384-r8jZRSsDBzYCLfvCeaJfxPd4DafZCgPMn9XWM4ZgJ1nVBYcyUBFd8XFugd6gxSYB" }, "api": { @@ -36,7 +36,7 @@ "APP_ORIGIN", "FASTNEAR_API_KEY" ], - "production": "https://multiagentic-229-api-dashboard-multiagency-7a9c91e37-ze.zephyrcloud.app", + "production": "https://multiagentic-244-api-dashboard-multiagency-83f2b3d10-ze.zephyrcloud.app", "integrity": "sha384-NBoulHMiE+WeadkiRukVbwvhaEhLEHwUDWkVP7YH1nbaVroBLnOcK+rpV2cDjpt1" }, "auth": { @@ -64,7 +64,7 @@ "secrets": [ "PROJECTS_DATABASE_URL" ], - "production": "https://multiagentic-228-everything-dev-projects-plugin-d-0ad7a8762-ze.zephyrcloud.app", + "production": "https://multiagentic-242-everything-dev-projects-plugin-d-f85acd777-ze.zephyrcloud.app", "integrity": "sha384-qPOhAJTs/T7AsUmqhbdUnrcWJc4R5zW8DtsCJLScSOOyQS46quiS1qmJ/PfVmlH8" }, "builders": { @@ -72,7 +72,7 @@ "secrets": [ "BUILDERS_DATABASE_URL" ], - "production": "https://multiagentic-227-everything-dev-builders-plugin-d-14c59c3f8-ze.zephyrcloud.app", + "production": "https://multiagentic-243-everything-dev-builders-plugin-d-1f619b063-ze.zephyrcloud.app", "integrity": "sha384-9Y9Z9Xt+7xuGxRxE7vLkkTOnoDRrql3qHYnjdyX4eBUqSJl+uFgWY3UK9bT11BLV" } } From e42990935eb2a3596b5871d98b4776a62e2c4eb6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 4 Oct 2026 14:51:16 +0000 Subject: [PATCH 010/128] chore: update staging deployment URLs [skip ci] --- bos.config.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/bos.config.json b/bos.config.json index 109e0088..3b73a0d7 100644 --- a/bos.config.json +++ b/bos.config.json @@ -13,10 +13,10 @@ "ui": { "name": "ui", "development": "local:ui", - "production": "https://multiagentic-241-ui-dashboard-multiagency-840f2efdd-ze.zephyrcloud.app", - "integrity": "sha384-I2BAfE8AscFQ7w/K3PiobZVBkNrj9I8A5VDQTAppArZiO8w+AefZhjPJGQflHCUY", - "ssr": "https://multiagentic-245-ui-dashboard-multiagency-08ff18b06-ze.zephyrcloud.app", - "ssrIntegrity": "sha384-r8jZRSsDBzYCLfvCeaJfxPd4DafZCgPMn9XWM4ZgJ1nVBYcyUBFd8XFugd6gxSYB" + "production": "https://multiagentic-256-ui-dashboard-multiagency-26ce1f98e-ze.zephyrcloud.app", + "integrity": "sha384-4gX3Nd5uA90/nkhkI8CdBNz7kXMAlkIlOt6SJBiWfSDeSw5YeuQPeMzm+1lYYqAo", + "ssr": "https://multiagentic-260-ui-dashboard-multiagency-3089ffd99-ze.zephyrcloud.app", + "ssrIntegrity": "sha384-egnSVj54FgE9theigQ2+e/blODHe5l+7Xs3Fe4MHllXEKY6CVYRRgogEnG2IYo82" }, "api": { "name": "api", @@ -37,8 +37,8 @@ "APP_ORIGIN", "FASTNEAR_API_KEY" ], - "production": "https://multiagentic-244-api-dashboard-multiagency-83f2b3d10-ze.zephyrcloud.app", - "integrity": "sha384-NBoulHMiE+WeadkiRukVbwvhaEhLEHwUDWkVP7YH1nbaVroBLnOcK+rpV2cDjpt1" + "production": "https://multiagentic-259-api-dashboard-multiagency-69abaf515-ze.zephyrcloud.app", + "integrity": "sha384-qmekkNZyj8V9kebDPUIiNsxMjN5mPN74tsTDVXbEySn3sVhsdLc8PUOek/O5zWCT" }, "auth": { "extends": "bos://auth.everything.near/auth.everything.dev#app.auth", @@ -65,7 +65,7 @@ "secrets": [ "PROJECTS_DATABASE_URL" ], - "production": "https://multiagentic-242-everything-dev-projects-plugin-d-f85acd777-ze.zephyrcloud.app", + "production": "https://multiagentic-258-everything-dev-projects-plugin-d-d44206d88-ze.zephyrcloud.app", "integrity": "sha384-qPOhAJTs/T7AsUmqhbdUnrcWJc4R5zW8DtsCJLScSOOyQS46quiS1qmJ/PfVmlH8" }, "builders": { @@ -75,8 +75,8 @@ "REGISTRY_TOKEN_TESTNET", "REGISTRY_TOKEN_MAINNET" ], - "production": "https://multiagentic-243-everything-dev-builders-plugin-d-1f619b063-ze.zephyrcloud.app", - "integrity": "sha384-9Y9Z9Xt+7xuGxRxE7vLkkTOnoDRrql3qHYnjdyX4eBUqSJl+uFgWY3UK9bT11BLV" + "production": "https://multiagentic-257-everything-dev-builders-plugin-d-1ec37713a-ze.zephyrcloud.app", + "integrity": "sha384-HVd3JBtogajDWoxVKGoFBiF93pb1sojI3kwzQgyhXrqVfldRO/Ynj6u87XeCpPpA" } } } From 6e0cdbf7549de8029f8632ae9f86dfe027a30827 Mon Sep 17 00:00:00 2001 From: James Waugh Date: Sun, 4 Oct 2026 17:12:46 -0400 Subject: [PATCH 011/128] fix(ui): show verified GitHub login to every viewer --- .changeset/verified-github-read-only.md | 5 +++++ ui/src/components/admin/contributors-section.tsx | 7 +------ .../_authenticated/admin/contributors/$nearAccount.tsx | 6 +++++- 3 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 .changeset/verified-github-read-only.md diff --git a/.changeset/verified-github-read-only.md b/.changeset/verified-github-read-only.md new file mode 100644 index 00000000..7b9491d3 --- /dev/null +++ b/.changeset/verified-github-read-only.md @@ -0,0 +1,5 @@ +--- +"ui": patch +--- + +A builder's verified GitHub login now shows in the page header of their profile, for every viewer, not only those who can edit it. A board member's read-only profile says the contribution board verifies their identity, instead of saying they manage their own profile. diff --git a/ui/src/components/admin/contributors-section.tsx b/ui/src/components/admin/contributors-section.tsx index 50f01093..f8ce4d47 100644 --- a/ui/src/components/admin/contributors-section.tsx +++ b/ui/src/components/admin/contributors-section.tsx @@ -44,7 +44,7 @@ import { adminContributorsListQueryOptions, refreshAfter } from "@/lib/queries"; type Contributor = Awaited>["data"][number]; -function VerifiedGithub({ login }: { login: string }) { +export function VerifiedGithub({ login }: { login: string }) { return ( Not registered yet )} - {contributor.githubLogin && ( - - - - )}
+ {contributor.githubLogin && } {contributorQuery.data?.canDelete && ( ) : (

- This builder manages their own profile, so only they or a platform admin can edit it. + {contributor.githubLogin + ? "This builder is a member of the contribution board, which verifies their identity, so only they or a platform admin can edit their profile." + : "This builder manages their own profile, so only they or a platform admin can edit it."}

)} From 420316e39b681d02ae8c8f96b7b064d380eb9a33 Mon Sep 17 00:00:00 2001 From: James Waugh Date: Sun, 4 Oct 2026 18:55:05 -0400 Subject: [PATCH 012/128] feat: platform Members page with services agreement recording --- .changeset/platform-members.md | 7 + api/src/contract.ts | 48 +++ api/src/index.ts | 13 + api/src/services/contributors.ts | 18 ++ .../integration/upstream-failures.test.ts | 39 +++ plugins/builders/src/contract.ts | 21 ++ plugins/builders/src/index.ts | 8 + plugins/builders/src/services/members.ts | 51 +++ plugins/builders/tests/members.test.ts | 26 ++ .../components/admin/contributors-section.tsx | 2 +- ui/src/lib/queries.ts | 10 + ui/src/routeTree.gen.ts | 23 ++ .../_layout/_authenticated/platform/index.tsx | 7 +- .../_authenticated/platform/members.tsx | 292 ++++++++++++++++++ ui/tests/platform-members.test.tsx | 101 ++++++ 15 files changed, 664 insertions(+), 2 deletions(-) create mode 100644 .changeset/platform-members.md create mode 100644 ui/src/routes/_layout/_authenticated/platform/members.tsx create mode 100644 ui/tests/platform-members.test.tsx diff --git a/.changeset/platform-members.md b/.changeset/platform-members.md new file mode 100644 index 00000000..27580859 --- /dev/null +++ b/.changeset/platform-members.md @@ -0,0 +1,7 @@ +--- +"@everything-dev/builders-plugin": minor +"api": minor +"ui": minor +--- + +Platform admins get a Members page (`/platform/members`, linked from Workspaces) listing every member of the contribution board, including those with only a testnet account: their verified GitHub login, kind and operator, admission on each network, and whether a services agreement is on file. From there a platform admin records or updates a member's agreement (version, date signed and a private proof that is never shown again). The builders plugin adds a platform-admin-only `listMembersWithAgreements`, and the API adds `members.list` and `members.recordAgreement`, both enforced as platform-admin-only by the plugin. diff --git a/api/src/contract.ts b/api/src/contract.ts index 534b6996..43240037 100644 --- a/api/src/contract.ts +++ b/api/src/contract.ts @@ -227,6 +227,34 @@ const contributor = z.object({ updatedAt: z.string(), }); +const registryNetwork = z.enum(["testnet", "mainnet"]); + +const registryMember = z.object({ + githubLogin: z.string(), + kind: z.enum(["human", "agent"]), + operatorGithubLogin: z.string().nullable(), + name: z.string().nullable(), + skills: z.array(z.string()), + nearAccount: z.string().nullable(), + accounts: z.array(z.object({ network: registryNetwork, account: z.string() })), + admissions: z.array( + z.object({ + network: registryNetwork, + status: z.enum(["admitted", "suspended", "removed"]), + proofUrl: z.string().nullable(), + admittedAt: z.string().nullable(), + }), + ), + agreement: z + .object({ + version: z.string(), + attestedAt: z.string(), + recordedBy: z.string(), + recordedAt: z.string(), + }) + .nullable(), +}); + const engagementParty = z.object({ id: z.string(), name: z.string(), slug: z.string() }); const engagementStatus = z.enum(["proposed", "active", "declined", "ended"]); @@ -1286,6 +1314,26 @@ export const contract = oc.router({ .errors({ UNAUTHORIZED, FORBIDDEN, NOT_FOUND, BAD_REQUEST }), }, + members: { + list: oc + .route({ method: "GET", path: "/platform/members" }) + .output(z.object({ data: z.array(registryMember) })) + .errors({ UNAUTHORIZED, FORBIDDEN }), + + recordAgreement: oc + .route({ method: "PUT", path: "/platform/members/{githubLogin}/agreement" }) + .input( + z.object({ + githubLogin: z.string().trim().toLowerCase().min(1).max(60), + version: z.string().trim().min(1).max(100), + attestedAt: z.iso.datetime(), + proof: z.string().trim().min(1).max(2000), + }), + ) + .output(z.object({ recorded: z.boolean() })) + .errors({ UNAUTHORIZED, FORBIDDEN, NOT_FOUND }), + }, + assignments: { list: oc .route({ method: "GET", path: "/admin/projects/{projectId}/contributors" }) diff --git a/api/src/index.ts b/api/src/index.ts index 6749c6dc..3f0205df 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -687,6 +687,19 @@ export default createPlugin.withPlugins()({ }), }, + members: { + list: builder.members.list + .use(auth.requireAuth) + .handler(async ({ context }) => runEffect(contributors.members(context))), + + recordAgreement: builder.members.recordAgreement + .use(auth.requireAuth) + .handler(async ({ context, input }) => { + await runEffect(contributors.recordAgreement(context, input)); + return { recorded: true }; + }), + }, + assignments: { list: builder.assignments.list .use(member) diff --git a/api/src/services/contributors.ts b/api/src/services/contributors.ts index bd579bc9..4c3ef4e3 100644 --- a/api/src/services/contributors.ts +++ b/api/src/services/contributors.ts @@ -96,6 +96,9 @@ const notYourBuilder = () => const asManager = (context: PluginContext): PluginContext => ({ ...context, trusted: true }); +const asOrpcError = (err: unknown) => + err instanceof ORPCError ? err : new ORPCError("INTERNAL_SERVER_ERROR", { message: String(err) }); + export function createContributorsService(db: Database, plugins: PluginsClient) { async function managesBuilder(manager: ManagerScope | undefined, nearAccount: string) { if (!manager) return false; @@ -328,6 +331,21 @@ export function createContributorsService(db: Database, plugins: PluginsClient) ); return result; }), + + members: (context: PluginContext) => + Effect.tryPromise({ + try: () => plugins.builders(context).listMembersWithAgreements({}), + catch: asOrpcError, + }), + + recordAgreement: ( + context: PluginContext, + input: { githubLogin: string; version: string; attestedAt: string; proof: string }, + ) => + Effect.tryPromise({ + try: () => plugins.builders(context).recordAgreement(input), + catch: asOrpcError, + }), }; } diff --git a/api/tests/integration/upstream-failures.test.ts b/api/tests/integration/upstream-failures.test.ts index 0291e0a2..6ba60126 100644 --- a/api/tests/integration/upstream-failures.test.ts +++ b/api/tests/integration/upstream-failures.test.ts @@ -114,6 +114,45 @@ describe("when upstream services fail", () => { return createContributorsService(db, plugins); } + test("lists board members for the caller the plugin is asked about", async () => { + const contexts: unknown[] = []; + const contributors = contributorsWith( + { listMembersWithAgreements: async () => ({ data: [{ githubLogin: "ada" }] }) }, + contexts, + ); + + const result = await Effect.runPromise(contributors.members({ userId: "platform" })); + + expect(result).toEqual({ data: [{ githubLogin: "ada" }] }); + expect(contexts.at(-1)).toEqual({ userId: "platform" }); + }); + + test("passes the plugin's refusal to record an agreement through unchanged", async () => { + const contributors = contributorsWith({ + recordAgreement: async () => { + throw new ORPCError("FORBIDDEN", { + message: "Only a platform admin records a services agreement", + }); + }, + }); + + const outcome = await Effect.runPromise( + Effect.either( + contributors.recordAgreement( + { userId: "manager" }, + { + githubLogin: "ada", + version: "v1", + attestedAt: "2026-10-01T00:00:00.000Z", + proof: "p", + }, + ), + ), + ); + + expect(Either.isLeft(outcome) && outcome.left).toMatchObject({ code: "FORBIDDEN" }); + }); + test("an assigned contributor without a builder profile gets an unregistered stub", async () => { await db .insert(projectContributors) diff --git a/plugins/builders/src/contract.ts b/plugins/builders/src/contract.ts index 9db854c1..ee8e5fdb 100644 --- a/plugins/builders/src/contract.ts +++ b/plugins/builders/src/contract.ts @@ -164,6 +164,27 @@ export const contract = oc.router({ .output(z.object({ data: MemberOutput, overwritten: z.array(z.string()) })) .errors({ UNAUTHORIZED, FORBIDDEN, BAD_REQUEST, CONFLICT }), + listMembersWithAgreements: oc + .route({ method: "GET", path: "/v1/admin/members" }) + .input(z.object({})) + .output( + z.object({ + data: z.array( + MemberOutput.extend({ + agreement: z + .object({ + version: z.string(), + attestedAt: z.iso.datetime(), + recordedBy: z.string(), + recordedAt: z.iso.datetime(), + }) + .nullable(), + }), + ), + }), + ) + .errors({ UNAUTHORIZED, FORBIDDEN }), + recordAgreement: oc .route({ method: "PUT", path: "/v1/members/{githubLogin}/agreement" }) .input( diff --git a/plugins/builders/src/index.ts b/plugins/builders/src/index.ts index 47b11ee5..8d4f4b7a 100644 --- a/plugins/builders/src/index.ts +++ b/plugins/builders/src/index.ts @@ -171,6 +171,14 @@ export default createPlugin({ return await runEffect(services.member.putMember(input)); }), + listMembersWithAgreements: builder.listMembersWithAgreements + .use(auth.requireAuth) + .handler(async ({ context }) => { + return { + data: await runEffect(services.member.listMembersWithAgreements(callerOf(context))), + }; + }), + recordAgreement: builder.recordAgreement .use(auth.requireAuth) .handler(async ({ input, context }) => { diff --git a/plugins/builders/src/services/members.ts b/plugins/builders/src/services/members.ts index f9124d96..65fb4872 100644 --- a/plugins/builders/src/services/members.ts +++ b/plugins/builders/src/services/members.ts @@ -61,6 +61,15 @@ export interface RecordedAgreement { recordedAt: string; } +export interface AgreementSummary { + version: string; + attestedAt: string; + recordedBy: string; + recordedAt: string; +} + +export type MemberWithAgreement = Member & { agreement: AgreementSummary | null }; + export interface MemberFilter { network?: Network; status?: AdmissionStatus; @@ -380,6 +389,43 @@ async function writeMember(tx: Transaction, input: MemberWrite) { return { data, overwritten }; } +async function listMembersWithAgreements( + db: Database, + caller: BuilderCaller, +): Promise { + if (!isPlatformAdmin(caller)) { + throw new ORPCError("FORBIDDEN", { message: "Only a platform admin sees services agreements" }); + } + const [members, agreements] = await Promise.all([ + listMembers(db, {}), + db + .select({ + githubLogin: builders.githubLogin, + version: builderAgreements.version, + attestedAt: builderAgreements.attestedAt, + recordedBy: builderAgreements.recordedBy, + recordedAt: builderAgreements.recordedAt, + }) + .from(builderAgreements) + .innerJoin(builders, eq(builders.id, builderAgreements.builderId)), + ]); + const byLogin = new Map(agreements.map((a) => [a.githubLogin, a])); + return members.map((member) => { + const agreement = byLogin.get(member.githubLogin); + return { + ...member, + agreement: agreement + ? { + version: agreement.version, + attestedAt: agreement.attestedAt.toISOString(), + recordedBy: agreement.recordedBy, + recordedAt: agreement.recordedAt.toISOString(), + } + : null, + }; + }); +} + async function recordAgreement( db: Database, githubLogin: string, @@ -421,6 +467,9 @@ export class MemberService extends Context.Tag("builders/MemberService")< ) => Effect.Effect<{ data: Member; overwritten: string[] }, ORPCError>; listMembers: (filter: MemberFilter) => Effect.Effect>; getMember: (githubLogin: string) => Effect.Effect>; + listMembersWithAgreements: ( + caller: BuilderCaller, + ) => Effect.Effect>; recordAgreement: ( githubLogin: string, agreement: Agreement, @@ -447,6 +496,8 @@ export const MemberServiceLive = Layer.effect( Effect.tryPromise({ try: () => listMembers(db, filter), catch: toOrpcError }), getMember: (githubLogin) => Effect.tryPromise({ try: () => readMember(db, githubLogin), catch: toOrpcError }), + listMembersWithAgreements: (caller) => + Effect.tryPromise({ try: () => listMembersWithAgreements(db, caller), catch: toOrpcError }), recordAgreement: (githubLogin, agreement, caller) => Effect.tryPromise({ try: () => recordAgreement(db, githubLogin, agreement, caller), diff --git a/plugins/builders/tests/members.test.ts b/plugins/builders/tests/members.test.ts index 65f848b0..d931cdc2 100644 --- a/plugins/builders/tests/members.test.ts +++ b/plugins/builders/tests/members.test.ts @@ -383,6 +383,32 @@ describe("writing board members", () => { code: "NOT_FOUND", }); }); + + test("is listed for a platform admin with every member, without its proof", async () => { + const signed = await admittedHuman("testnet"); + const unsigned = await admittedHuman("testnet"); + await record(platformAdminClient(), signed); + + const { data } = await platformAdminClient().listMembersWithAgreements({}); + const byLogin = new Map(data.map((m: any) => [m.githubLogin, m])); + + expect(byLogin.get(signed).agreement).toEqual({ + version: agreement.version, + attestedAt: agreement.attestedAt, + recordedBy: "platform", + recordedAt: expect.any(String), + }); + expect(byLogin.get(unsigned).agreement).toBeNull(); + expect(byLogin.get(unsigned).nearAccount).toBeNull(); + expect(JSON.stringify(data)).not.toContain(agreement.proof); + }); + + test.each([ + ["anyone signed out", () => anonymousClient(), "UNAUTHORIZED"], + ["an Agency manager", () => agencyClient(), "FORBIDDEN"], + ])("is not listed for %s", async (_name, client, code) => { + await expect(client().listMembersWithAgreements({})).rejects.toMatchObject({ code }); + }); }); describe("the mainnet account", () => { diff --git a/ui/src/components/admin/contributors-section.tsx b/ui/src/components/admin/contributors-section.tsx index 50f01093..f3dbf10e 100644 --- a/ui/src/components/admin/contributors-section.tsx +++ b/ui/src/components/admin/contributors-section.tsx @@ -44,7 +44,7 @@ import { adminContributorsListQueryOptions, refreshAfter } from "@/lib/queries"; type Contributor = Awaited>["data"][number]; -function VerifiedGithub({ login }: { login: string }) { +export function VerifiedGithub({ login }: { login: string }) { return (
apiClient.members.list(), + retry: false, + }); +} + export const adminAssignmentsListQueryKey = ["admin", "assignments", "list"] as const; export function adminAssignmentsListQueryOptions(apiClient: ApiClient) { diff --git a/ui/src/routeTree.gen.ts b/ui/src/routeTree.gen.ts index 23eedc5d..f91208c3 100644 --- a/ui/src/routeTree.gen.ts +++ b/ui/src/routeTree.gen.ts @@ -34,6 +34,7 @@ import { Route as LayoutAuthenticatedAdminRouteRouteImport } from './routes/_lay import { Route as LayoutAuthenticatedPlatformIndexRouteImport } from './routes/_layout/_authenticated/platform/index' import { Route as LayoutAuthenticatedClientIndexRouteImport } from './routes/_layout/_authenticated/client/index' import { Route as LayoutAuthenticatedAdminIndexRouteImport } from './routes/_layout/_authenticated/admin/index' +import { Route as LayoutAuthenticatedPlatformMembersRouteImport } from './routes/_layout/_authenticated/platform/members' import { Route as LayoutAuthenticatedClientReportsRouteImport } from './routes/_layout/_authenticated/client/reports' import { Route as LayoutAuthenticatedAdminSettingsRouteImport } from './routes/_layout/_authenticated/admin/settings' import { Route as LayoutAuthenticatedAdminMembersRouteImport } from './routes/_layout/_authenticated/admin/members' @@ -193,6 +194,12 @@ const LayoutAuthenticatedAdminIndexRoute = path: '/', getParentRoute: () => LayoutAuthenticatedAdminRouteRoute, } as any) +const LayoutAuthenticatedPlatformMembersRoute = + LayoutAuthenticatedPlatformMembersRouteImport.update({ + id: '/members', + path: '/members', + getParentRoute: () => LayoutAuthenticatedPlatformRouteRoute, + } as any) const LayoutAuthenticatedClientReportsRoute = LayoutAuthenticatedClientReportsRouteImport.update({ id: '/reports', @@ -363,6 +370,7 @@ export interface FileRoutesByFullPath { '/admin/members': typeof LayoutAuthenticatedAdminMembersRoute '/admin/settings': typeof LayoutAuthenticatedAdminSettingsRoute '/client/reports': typeof LayoutAuthenticatedClientReportsRoute + '/platform/members': typeof LayoutAuthenticatedPlatformMembersRoute '/admin/': typeof LayoutAuthenticatedAdminIndexRoute '/client/': typeof LayoutAuthenticatedClientIndexRoute '/platform/': typeof LayoutAuthenticatedPlatformIndexRoute @@ -408,6 +416,7 @@ export interface FileRoutesByTo { '/admin/members': typeof LayoutAuthenticatedAdminMembersRoute '/admin/settings': typeof LayoutAuthenticatedAdminSettingsRoute '/client/reports': typeof LayoutAuthenticatedClientReportsRoute + '/platform/members': typeof LayoutAuthenticatedPlatformMembersRoute '/admin': typeof LayoutAuthenticatedAdminIndexRoute '/client': typeof LayoutAuthenticatedClientIndexRoute '/platform': typeof LayoutAuthenticatedPlatformIndexRoute @@ -460,6 +469,7 @@ export interface FileRoutesById { '/_layout/_authenticated/admin/members': typeof LayoutAuthenticatedAdminMembersRoute '/_layout/_authenticated/admin/settings': typeof LayoutAuthenticatedAdminSettingsRoute '/_layout/_authenticated/client/reports': typeof LayoutAuthenticatedClientReportsRoute + '/_layout/_authenticated/platform/members': typeof LayoutAuthenticatedPlatformMembersRoute '/_layout/_authenticated/admin/': typeof LayoutAuthenticatedAdminIndexRoute '/_layout/_authenticated/client/': typeof LayoutAuthenticatedClientIndexRoute '/_layout/_authenticated/platform/': typeof LayoutAuthenticatedPlatformIndexRoute @@ -511,6 +521,7 @@ export interface FileRouteTypes { | '/admin/members' | '/admin/settings' | '/client/reports' + | '/platform/members' | '/admin/' | '/client/' | '/platform/' @@ -556,6 +567,7 @@ export interface FileRouteTypes { | '/admin/members' | '/admin/settings' | '/client/reports' + | '/platform/members' | '/admin' | '/client' | '/platform' @@ -607,6 +619,7 @@ export interface FileRouteTypes { | '/_layout/_authenticated/admin/members' | '/_layout/_authenticated/admin/settings' | '/_layout/_authenticated/client/reports' + | '/_layout/_authenticated/platform/members' | '/_layout/_authenticated/admin/' | '/_layout/_authenticated/client/' | '/_layout/_authenticated/platform/' @@ -813,6 +826,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutAuthenticatedAdminIndexRouteImport parentRoute: typeof LayoutAuthenticatedAdminRouteRoute } + '/_layout/_authenticated/platform/members': { + id: '/_layout/_authenticated/platform/members' + path: '/members' + fullPath: '/platform/members' + preLoaderRoute: typeof LayoutAuthenticatedPlatformMembersRouteImport + parentRoute: typeof LayoutAuthenticatedPlatformRouteRoute + } '/_layout/_authenticated/client/reports': { id: '/_layout/_authenticated/client/reports' path: '/reports' @@ -1096,11 +1116,14 @@ const LayoutAuthenticatedClientRouteRouteWithChildren = ) interface LayoutAuthenticatedPlatformRouteRouteChildren { + LayoutAuthenticatedPlatformMembersRoute: typeof LayoutAuthenticatedPlatformMembersRoute LayoutAuthenticatedPlatformIndexRoute: typeof LayoutAuthenticatedPlatformIndexRoute } const LayoutAuthenticatedPlatformRouteRouteChildren: LayoutAuthenticatedPlatformRouteRouteChildren = { + LayoutAuthenticatedPlatformMembersRoute: + LayoutAuthenticatedPlatformMembersRoute, LayoutAuthenticatedPlatformIndexRoute: LayoutAuthenticatedPlatformIndexRoute, } diff --git a/ui/src/routes/_layout/_authenticated/platform/index.tsx b/ui/src/routes/_layout/_authenticated/platform/index.tsx index 04b6eb4a..584da8bb 100644 --- a/ui/src/routes/_layout/_authenticated/platform/index.tsx +++ b/ui/src/routes/_layout/_authenticated/platform/index.tsx @@ -1,5 +1,5 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, Link } from "@tanstack/react-router"; import { useState } from "react"; import { toast } from "sonner"; import { @@ -86,6 +86,11 @@ function PlatformOrgs() { + Members + + } /> diff --git a/ui/src/routes/_layout/_authenticated/platform/members.tsx b/ui/src/routes/_layout/_authenticated/platform/members.tsx new file mode 100644 index 00000000..4120fedb --- /dev/null +++ b/ui/src/routes/_layout/_authenticated/platform/members.tsx @@ -0,0 +1,292 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { createFileRoute, Link } from "@tanstack/react-router"; +import { useState } from "react"; +import { toast } from "sonner"; +import { + Badge, + Button, + Card, + CardAction, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, + DataTable, + Field, + FieldDescription, + FieldGroup, + FieldLabel, + Input, + Textarea, +} from "@/components"; +import { VerifiedGithub } from "@/components/admin/contributors-section"; +import { LoadError } from "@/components/load-error"; +import { PageHeader } from "@/components/page-header"; +import type { ColumnDef } from "@/components/ui/data-table"; +import type { ApiClient } from "@/lib/api"; +import { useApiClient } from "@/lib/api"; +import { platformMembersQueryKey, platformMembersQueryOptions } from "@/lib/queries"; + +export const Route = createFileRoute("/_layout/_authenticated/platform/members")({ + head: () => ({ + meta: [{ title: "Members | Platform" }], + }), + component: PlatformMembers, +}); + +type Member = Awaited>["data"][number]; +type Network = Member["admissions"][number]["network"]; + +function admissionOn(member: Member, network: Network) { + const admission = member.admissions.find((a) => a.network === network); + if (!admission) return —; + return ( + + {admission.status} + + ); +} + +function PlatformMembers() { + const apiClient = useApiClient(); + const membersQuery = useQuery(platformMembersQueryOptions(apiClient)); + const [recording, setRecording] = useState(null); + const members = membersQuery.data?.data ?? []; + + const columns: ColumnDef[] = [ + { + id: "githubLogin", + header: "Member", + accessorKey: "githubLogin", + cell: ({ row }) => ( +
+ + {row.original.name && ( + {row.original.name} + )} +
+ ), + }, + { + id: "kind", + header: "Kind", + accessorKey: "kind", + cell: ({ row }) => + row.original.operatorGithubLogin ? ( + + agent ·{" "} + @{row.original.operatorGithubLogin} + + ) : ( + row.original.kind + ), + }, + { + id: "testnet", + header: "Testnet", + accessorFn: (row) => row.admissions.find((a) => a.network === "testnet")?.status ?? "", + cell: ({ row }) => admissionOn(row.original, "testnet"), + }, + { + id: "mainnet", + header: "Mainnet", + accessorFn: (row) => row.admissions.find((a) => a.network === "mainnet")?.status ?? "", + cell: ({ row }) => admissionOn(row.original, "mainnet"), + }, + { + id: "agreement", + header: "Services agreement", + accessorFn: (row) => row.agreement?.version ?? "", + cell: ({ row }) => + row.original.agreement ? ( + + {row.original.agreement.version}{" "} + + · {row.original.agreement.attestedAt.slice(0, 10)} + + + ) : ( + None + ), + }, + { + id: "actions", + header: "", + enableSorting: false, + enableHiding: false, + cell: ({ row }) => ( +
+ +
+ ), + }, + ]; + + return ( +
+ + Workspaces + + } + /> + + {recording && ( + m.githubLogin === recording)?.agreement ?? null} + onDone={() => setRecording(null)} + /> + )} + + + + +

Board members

+
+ + Members come from the board's join flow. The board verifies each GitHub login and NEAR + account; agreements are recorded here. + + + {members.length} + +
+ + {membersQuery.isError ? ( + void membersQuery.refetch()} + /> + ) : ( + + )} + +
+
+ ); +} + +function RecordAgreementForm({ + githubLogin, + existing, + onDone, +}: { + githubLogin: string; + existing: Member["agreement"]; + onDone: () => void; +}) { + const apiClient = useApiClient(); + const queryClient = useQueryClient(); + const [version, setVersion] = useState(existing?.version ?? ""); + const [attestedOn, setAttestedOn] = useState(existing?.attestedAt.slice(0, 10) ?? ""); + const [proof, setProof] = useState(""); + + const recordMutation = useMutation({ + mutationFn: () => + apiClient.members.recordAgreement({ + githubLogin, + version: version.trim(), + attestedAt: new Date(`${attestedOn}T00:00:00.000Z`).toISOString(), + proof: proof.trim(), + }), + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: platformMembersQueryKey }); + toast.success(`Agreement recorded for @${githubLogin}`); + onDone(); + }, + onError: (err: Error) => toast.error(err.message || "Failed to record the agreement"), + }); + + const canSubmit = Boolean(version.trim() && attestedOn && proof.trim()); + const isPending = recordMutation.isPending; + + return ( + + + +

Services agreement for @{githubLogin}

+
+ + Recording an agreement makes this member internal. The proof stays private: it's never + shown again, including here. + +
+ { + e.preventDefault(); + if (canSubmit && !isPending) recordMutation.mutate(); + }} + > + + +
+ + Agreement version + setVersion(e.target.value)} + placeholder="e.g. 2026-09" + disabled={isPending} + /> + + + Signed on + setAttestedOn(e.target.value)} + disabled={isPending} + /> + +
+ + Proof +