Publish curated ITWorx_MCP_Hub source from 9ec6419178e8ddc76a698e84bde72d5ff4472411

This commit is contained in:
Public Source Publisher committed 2026-10-01 09:39:30 +00:00
1 parent 5ff5c807d3
commit f26354e16c
20 files changed
+1627 -72

No files matched your search

+1 -1
View File
@@ -1,3 +1,3 @@
# Public source export
This source snapshot was generated from the private canonical repository at revision `e1fd00a3172eaaec29bf8f1e6e47f96d175c31ff`. It contains no private Git history or operational evidence. Changes are published from the canonical repository.
This source snapshot was generated from the private canonical repository at revision `9ec6419178e8ddc76a698e84bde72d5ff4472411`. It contains no private Git history or operational evidence. Changes are published from the canonical repository.
+51 -26
View File
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"sourceRevision": "e1fd00a3172eaaec29bf8f1e6e47f96d175c31ff",
"sourceRevision": "9ec6419178e8ddc76a698e84bde72d5ff4472411",
"files": [
{
"path": ".dockerignore",
@@ -509,8 +509,8 @@
},
{
"path": "catalog/policies/041-appops-governed-operations.yaml",
"sha256": "1426694df3ec9f8d4691bce055abc21bd991d2a0041ba4b3b0e6619de7e747c1",
"bytes": 1024
"sha256": "915a080783aaf99145699b30d1556f16fd799d4c267f588d1d4dc06fac2ee047",
"bytes": 1068
},
{
"path": "catalog/policies/042-stitch-governed-design-writes.yaml",
@@ -584,8 +584,8 @@
},
{
"path": "catalog/tools/gitea-branch-protection-ensure.yaml",
"sha256": "b0750a7de2b10c8c0403444e96bf9a9ef6c60c3c184f02bb8322de5d732f2bff",
"bytes": 822
"sha256": "bb072d1f18a17d7bbe9f7e628fd9c93ac7d8af7be2065b40d6195898e42502ff",
"bytes": 968
},
{
"path": "catalog/tools/gitea-branch-protection-get.yaml",
@@ -594,8 +594,8 @@
},
{
"path": "catalog/tools/gitea-branch-protection-preview.yaml",
"sha256": "a31c94a963c8d384bfbd67706dec8a82ddf18a889f8749ac6f79d41b99e6670d",
"bytes": 735
"sha256": "e975e08cc47d619f61489cb1eb78ff6d8c0eca2fc281301f21bca83dc74e14ea",
"bytes": 787
},
{
"path": "catalog/tools/gitea-capabilities-get.yaml",
@@ -709,18 +709,18 @@
},
{
"path": "catalog/tools/gitea-portfolio-governance-apply.yaml",
"sha256": "7b4f20d29390a005e13ed4d4a3726dadb0d19d15814ad61d4c265b717ce06153",
"bytes": 960
"sha256": "6c096efb328d513b7b7995c79e9cb03caf3ba85017d3af4941101baaae43f7b2",
"bytes": 1183
},
{
"path": "catalog/tools/gitea-portfolio-governance-preview.yaml",
"sha256": "dcf866336af236d81dde3476988f066d1b86bfb2e8ae3432af30a068cab18a4b",
"bytes": 810
"sha256": "1ecb3aa9ae7b60871b48066221ae02b76c156422d96f4e86526196d8b9914d72",
"bytes": 819
},
{
"path": "catalog/tools/gitea-portfolio-governance-verify.yaml",
"sha256": "59acb31c46829ac985ec9b1291b08c5eadd03d657be3d42b6c23fa9cfe12f86e",
"bytes": 805
"sha256": "425ce50c7d8fda4ea745fb631da869fe2023a8f1f846a4c7cece5d472f00416a",
"bytes": 814
},
{
"path": "catalog/tools/gitea-portfolio-health.yaml",
@@ -1402,6 +1402,26 @@
"sha256": "e90091e39c5e63285954f9f322f8afe2c2c35575152d13f7b31e29166c519648",
"bytes": 659
},
{
"path": "catalog/tools/server-appops-deployment-status.yaml",
"sha256": "d766ff2982f8b3232d931160f3e1c369e94eb1ac01b4a44b6b15ff0d1d7ad658",
"bytes": 661
},
{
"path": "catalog/tools/server-appops-self-upgrade-apply.yaml",
"sha256": "85f3d7f7cfb09312704881b48154ba0af71e82da22985ed7cf31ccb4b3fbe56c",
"bytes": 1235
},
{
"path": "catalog/tools/server-appops-self-upgrade-preflight.yaml",
"sha256": "9eae687b97913f99642161c9c8bd5a1f1627671e1150fcf180366d1a77d18e6f",
"bytes": 1011
},
{
"path": "catalog/tools/server-appops-self-upgrade-status.yaml",
"sha256": "500515ffee25623be12e847b4f81edb43edc13cee294c7174bf97be2a7cee8c5",
"bytes": 766
},
{
"path": "catalog/tools/server-audit-search.yaml",
"sha256": "d43c06ce0b1d423a0ce13262b9189ffd645d3ea6cf2c3e9ea3fd4740c7fa293e",
@@ -1634,8 +1654,8 @@
},
{
"path": "connectors/gitea/server.py",
"sha256": "7da7c3be6d259ef2c897f52c82771716a602559f7671c794ec6380634ffdc91e",
"bytes": 209422
"sha256": "d8c5be1efed22d99dc287a83fd78156e306afa731f6b233b4d076bb5736152b4",
"bytes": 227648
},
{
"path": "connectors/knowledge/__init__.py",
@@ -2554,8 +2574,8 @@
},
{
"path": "services/control_api/contextforge/fake.py",
"sha256": "74cf3f097ea40c761d6bed05ee457d9fb0eea5bc603eba565da3e2da9b7d8023",
"bytes": 18589
"sha256": "7cd684a569d5df1624af6f449e4f13199b8b113c1244dd4afec0de2121421fc4",
"bytes": 20697
},
{
"path": "services/control_api/contextforge/models.py",
@@ -2734,13 +2754,13 @@
},
{
"path": "services/runtime/connector_appops.py",
"sha256": "976f81079e1deb7b0eea2445e80da21bc5057ed788058b646f0afb8834316dfd",
"bytes": 9190
"sha256": "22360e7ac393d2a72cacd8309a6e68f2d232603271649992706a7f6c5ee3a83a",
"bytes": 9474
},
{
"path": "services/runtime/connector_gitea.py",
"sha256": "144b4e7d8fc1a8fb37dc011dab2bdd169fc869382a361e35aee435ddc7f59a34",
"bytes": 37459
"sha256": "c89874089d1bdde3020864e9222052d2470970ad7c4aa5677201cbfbc4a88eea",
"bytes": 37958
},
{
"path": "services/runtime/connector_ingestion.py",
@@ -2887,6 +2907,11 @@
"sha256": "006648857d313b8f7fcaa62b86d5f453aa565a75a348aefe7b8557e3b4ba8620",
"bytes": 63
},
{
"path": "tests/fixtures/projectbrain-managed-validation.yml",
"sha256": "2f78d37ff51f38f0a3b0c9e222eb2dbf83d5108aee3615eb53f2c01283f37f34",
"bytes": 9185
},
{
"path": "tests/fixtures/security/wp160-untrusted-inputs.json",
"sha256": "779ab274237df2dfd4d23f4b0fe2c21621594ca5a3f64aeef586ba448f702a5c",
@@ -2899,8 +2924,8 @@
},
{
"path": "tests/test_appops_connector.py",
"sha256": "5520aa212d9a175da22cb4bb584f8c36811e923003f3d13d468ebfd224f334f4",
"bytes": 5257
"sha256": "4e088d187b239cd1e8b81b7c5d72aed27eddae78491678564bfe2776d37913be",
"bytes": 9448
},
{
"path": "tests/test_auth.py",
@@ -2914,7 +2939,7 @@
},
{
"path": "tests/test_catalog.py",
"sha256": "e591b502f2dd07ae165b5cafa8f0a0e42a84530b8a352c963116cb803e865cb8",
"sha256": "adbe584172170fa17fe21c0e6a1953729e9b83190845d8e9d053b0d2cef4b1c6",
"bytes": 16467
},
{
@@ -3039,8 +3064,8 @@
},
{
"path": "tests/test_gitea_connector.py",
"sha256": "62c25f2953cd6d0d02c59d07925430886f87b5f04f6f995ef4c8168a77e59483",
"bytes": 97203
"sha256": "c6389e924471d392f1a19cae48e9c409b349223b76faa34c20b9ae4c7894ede2",
"bytes": 128684
},
{
"path": "tests/test_gitea_merge_eligibility.py",
@@ -17,7 +17,8 @@
"server.project.reconcile",
"server.deployment.target.enroll",
"server.deployment.apply",
"server.deployment.rollback"
"server.deployment.rollback",
"server.appops.self_upgrade.apply"
]
},
"conditions": {"requireEnabledClient": true, "requireBinding": true, "maxArgumentBytes": 65536},
@@ -1 +1 @@
{"apiVersion":"hub.example.com/v1alpha1","kind":"ToolManifest","metadata":{"name":"Gitea Branch Protection Ensure","slug":"gitea-branch-protection-ensure"},"spec":{"canonicalName":"gitea.branchprotection.ensure","version":"1.8.0","connectorType":"gitea","description":"Apply one unchanged governance preview and verify managed validation plus default-branch protection.","riskClass":"R2","readOnly":false,"idempotent":true,"inputSchema":{"type":"object","additionalProperties":false,"properties":{"repositoryRef":{"type":"string"},"expectedPlanHash":{"type":"string","pattern":"^[0-9a-f]{64}$"}},"required":["repositoryRef","expectedPlanHash"]},"outputSchema":{"type":"object"},"limits":{"timeoutMs":30000,"maxOutputBytes":262144,"maxItems":1},"redactionProfile":"documents","requiredScopes":["project:{project}:write"]}}
{"apiVersion":"hub.example.com/v1alpha1","kind":"ToolManifest","metadata":{"name":"Gitea Branch Protection Ensure","slug":"gitea-branch-protection-ensure"},"spec":{"canonicalName":"gitea.branchprotection.ensure","version":"1.9.0","connectorType":"gitea","description":"Apply one unchanged governance preview with an explicitly hashed scope; ProjectBrain protection-only scope adds required status checks without rewriting its workflow.","riskClass":"R2","readOnly":false,"idempotent":true,"inputSchema":{"type":"object","additionalProperties":false,"properties":{"repositoryRef":{"type":"string"},"expectedPlanHash":{"type":"string","pattern":"^[0-9a-f]{64}$"},"applyScope":{"type":"string","enum":["all","protection_only"],"default":"all"}},"required":["repositoryRef","expectedPlanHash"]},"outputSchema":{"type":"object"},"limits":{"timeoutMs":30000,"maxOutputBytes":262144,"maxItems":1},"redactionProfile":"documents","requiredScopes":["project:{project}:write"]}}
@@ -1 +1 @@
{"apiVersion":"hub.example.com/v1alpha1","kind":"ToolManifest","metadata":{"name":"Gitea Branch Protection Preview","slug":"gitea-branch-protection-preview"},"spec":{"canonicalName":"gitea.branchprotection.preview","version":"1.8.0","connectorType":"gitea","description":"Preview the exact managed-validation and default-branch protection diff and return a plan hash.","riskClass":"R1","readOnly":true,"idempotent":true,"inputSchema":{"type":"object","additionalProperties":false,"properties":{"repositoryRef":{"type":"string"}},"required":["repositoryRef"]},"outputSchema":{"type":"object"},"limits":{"timeoutMs":20000,"maxOutputBytes":262144,"maxItems":1},"redactionProfile":"documents","requiredScopes":["project:{project}:read"]}}
{"apiVersion":"hub.example.com/v1alpha1","kind":"ToolManifest","metadata":{"name":"Gitea Branch Protection Preview","slug":"gitea-branch-protection-preview"},"spec":{"canonicalName":"gitea.branchprotection.preview","version":"1.9.0","connectorType":"gitea","description":"Preview the exact managed-validation profile and default-branch protection diff, including a separately hashed ProjectBrain protection-only option.","riskClass":"R1","readOnly":true,"idempotent":true,"inputSchema":{"type":"object","additionalProperties":false,"properties":{"repositoryRef":{"type":"string"}},"required":["repositoryRef"]},"outputSchema":{"type":"object"},"limits":{"timeoutMs":20000,"maxOutputBytes":262144,"maxItems":1},"redactionProfile":"documents","requiredScopes":["project:{project}:read"]}}
@@ -1 +1 @@
{"apiVersion":"hub.example.com/v1alpha1","kind":"ToolManifest","metadata":{"name":"Gitea Portfolio Governance Apply","slug":"gitea-portfolio-governance-apply"},"spec":{"canonicalName":"gitea.portfolio.governance.apply","version":"1.8.0","connectorType":"gitea","description":"Apply and verify at most five exact repository governance plans in one bounded batch.","riskClass":"R2","readOnly":false,"idempotent":true,"inputSchema":{"type":"object","additionalProperties":false,"properties":{"plans":{"type":"array","minItems":1,"maxItems":5,"uniqueItems":true,"items":{"type":"object","additionalProperties":false,"properties":{"repositoryRef":{"type":"string"},"planHash":{"type":"string","pattern":"^[0-9a-f]{64}$"}},"required":["repositoryRef","planHash"]}}},"required":["plans"]},"outputSchema":{"type":"object"},"limits":{"timeoutMs":120000,"maxOutputBytes":524288,"maxItems":5},"redactionProfile":"documents","requiredScopes":["project:{project}:write"]}}
{"apiVersion":"hub.example.com/v1alpha1","kind":"ToolManifest","metadata":{"name":"Gitea Portfolio Governance Apply","slug":"gitea-portfolio-governance-apply"},"spec":{"canonicalName":"gitea.portfolio.governance.apply","version":"1.9.0","connectorType":"gitea","description":"Apply and verify at most five exact repository governance plans, including the separately hashed ProjectBrain protection-only scope.","riskClass":"R2","readOnly":false,"idempotent":true,"inputSchema":{"type":"object","additionalProperties":false,"properties":{"plans":{"type":"array","minItems":1,"maxItems":5,"uniqueItems":true,"items":{"type":"object","additionalProperties":false,"properties":{"repositoryRef":{"type":"string"},"planHash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"Use planHash for all scope or protectionOnlyPlanHash for protection_only scope."},"applyScope":{"type":"string","enum":["all","protection_only"],"default":"all"}},"required":["repositoryRef","planHash"]}}},"required":["plans"]},"outputSchema":{"type":"object"},"limits":{"timeoutMs":120000,"maxOutputBytes":524288,"maxItems":5},"redactionProfile":"documents","requiredScopes":["project:{project}:write"]}}
@@ -1 +1 @@
{"apiVersion":"hub.example.com/v1alpha1","kind":"ToolManifest","metadata":{"name":"Gitea Portfolio Governance Preview","slug":"gitea-portfolio-governance-preview"},"spec":{"canonicalName":"gitea.portfolio.governance.preview","version":"1.8.0","connectorType":"gitea","description":"Preview managed validation and branch protection for at most 25 explicit repositories.","riskClass":"R1","readOnly":true,"idempotent":true,"inputSchema":{"type":"object","additionalProperties":false,"properties":{"repositoryRefs":{"type":"array","minItems":1,"maxItems":25,"uniqueItems":true,"items":{"type":"string"}}},"required":["repositoryRefs"]},"outputSchema":{"type":"object"},"limits":{"timeoutMs":60000,"maxOutputBytes":524288,"maxItems":25},"redactionProfile":"documents","requiredScopes":["project:{project}:read"]}}
{"apiVersion":"hub.example.com/v1alpha1","kind":"ToolManifest","metadata":{"name":"Gitea Portfolio Governance Preview","slug":"gitea-portfolio-governance-preview"},"spec":{"canonicalName":"gitea.portfolio.governance.preview","version":"1.9.0","connectorType":"gitea","description":"Preview managed-validation profiles and branch protection for at most 25 explicit repositories.","riskClass":"R1","readOnly":true,"idempotent":true,"inputSchema":{"type":"object","additionalProperties":false,"properties":{"repositoryRefs":{"type":"array","minItems":1,"maxItems":25,"uniqueItems":true,"items":{"type":"string"}}},"required":["repositoryRefs"]},"outputSchema":{"type":"object"},"limits":{"timeoutMs":60000,"maxOutputBytes":524288,"maxItems":25},"redactionProfile":"documents","requiredScopes":["project:{project}:read"]}}
@@ -1 +1 @@
{"apiVersion":"hub.example.com/v1alpha1","kind":"ToolManifest","metadata":{"name":"Gitea Portfolio Governance Verify","slug":"gitea-portfolio-governance-verify"},"spec":{"canonicalName":"gitea.portfolio.governance.verify","version":"1.8.0","connectorType":"gitea","description":"Verify managed validation and default-branch protection for at most 25 repositories.","riskClass":"R1","readOnly":true,"idempotent":true,"inputSchema":{"type":"object","additionalProperties":false,"properties":{"repositoryRefs":{"type":"array","minItems":1,"maxItems":25,"uniqueItems":true,"items":{"type":"string"}}},"required":["repositoryRefs"]},"outputSchema":{"type":"object"},"limits":{"timeoutMs":60000,"maxOutputBytes":524288,"maxItems":25},"redactionProfile":"documents","requiredScopes":["project:{project}:read"]}}
{"apiVersion":"hub.example.com/v1alpha1","kind":"ToolManifest","metadata":{"name":"Gitea Portfolio Governance Verify","slug":"gitea-portfolio-governance-verify"},"spec":{"canonicalName":"gitea.portfolio.governance.verify","version":"1.9.0","connectorType":"gitea","description":"Verify managed-validation profiles and default-branch protection for at most 25 repositories.","riskClass":"R1","readOnly":true,"idempotent":true,"inputSchema":{"type":"object","additionalProperties":false,"properties":{"repositoryRefs":{"type":"array","minItems":1,"maxItems":25,"uniqueItems":true,"items":{"type":"string"}}},"required":["repositoryRefs"]},"outputSchema":{"type":"object"},"limits":{"timeoutMs":60000,"maxOutputBytes":524288,"maxItems":25},"redactionProfile":"documents","requiredScopes":["project:{project}:read"]}}
@@ -0,0 +1 @@
{"apiVersion":"hub.example.com/v1alpha1","kind":"ToolManifest","metadata":{"name":"Server AppOps Deployment Status","slug":"server-appops-deployment-status"},"spec":{"canonicalName":"server.appops.deployment.status","version":"1.4.0","connectorType":"appops","description":"Read the exact source repository, revision, source tree and health of the running AppOps gateway image.","riskClass":"R1","readOnly":true,"idempotent":true,"inputSchema":{"type":"object","additionalProperties":false},"outputSchema":{"type":"object"},"limits":{"timeoutMs":30000,"maxOutputBytes":65536,"maxItems":1},"redactionProfile":"logs","requiredScopes":["project:{project}:read"]}}
@@ -0,0 +1 @@
{"apiVersion":"hub.example.com/v1alpha1","kind":"ToolManifest","metadata":{"name":"Server AppOps Self-Upgrade Apply","slug":"server-appops-self-upgrade-apply"},"spec":{"canonicalName":"server.appops.self_upgrade.apply","version":"1.4.0","connectorType":"appops","description":"Request only the preflighted exact AppOps revision through its private single-instance controller, bound to the preflight fingerprint and idempotency key.","riskClass":"R3","readOnly":false,"idempotent":true,"inputSchema":{"type":"object","additionalProperties":false,"properties":{"repository":{"type":"string","const":"jens/unraid-appops-gateway"},"revision":{"type":"string","pattern":"^[0-9a-f]{40}$"},"reason":{"type":"string","minLength":8,"maxLength":500},"expected_preflight_fingerprint":{"type":"string","pattern":"^[0-9a-f]{64}$"},"idempotency_key":{"type":"string","pattern":"^[A-Za-z0-9._:-]{8,128}$"},"expected_current_revision":{"type":"string","pattern":"^[0-9a-f]{40}$"}},"required":["repository","revision","reason","expected_preflight_fingerprint","idempotency_key"]},"outputSchema":{"type":"object"},"limits":{"timeoutMs":60000,"maxOutputBytes":262144,"maxItems":1},"redactionProfile":"logs","requiredScopes":["project:{project}:write"]}}
@@ -0,0 +1 @@
{"apiVersion":"hub.example.com/v1alpha1","kind":"ToolManifest","metadata":{"name":"Server AppOps Self-Upgrade Preflight","slug":"server-appops-self-upgrade-preflight"},"spec":{"canonicalName":"server.appops.self_upgrade.preflight","version":"1.4.0","connectorType":"appops","description":"Check one exact protected-main AppOps revision and current-revision expectation through the private deployment controller.","riskClass":"R1","readOnly":true,"idempotent":true,"inputSchema":{"type":"object","additionalProperties":false,"properties":{"repository":{"type":"string","const":"jens/unraid-appops-gateway"},"revision":{"type":"string","pattern":"^[0-9a-f]{40}$"},"reason":{"type":"string","minLength":8,"maxLength":500},"expected_current_revision":{"type":"string","pattern":"^[0-9a-f]{40}$"}},"required":["repository","revision","reason"]},"outputSchema":{"type":"object"},"limits":{"timeoutMs":30000,"maxOutputBytes":262144,"maxItems":1},"redactionProfile":"logs","requiredScopes":["project:{project}:read"]}}
@@ -0,0 +1 @@
{"apiVersion":"hub.example.com/v1alpha1","kind":"ToolManifest","metadata":{"name":"Server AppOps Self-Upgrade Status","slug":"server-appops-self-upgrade-status"},"spec":{"canonicalName":"server.appops.self_upgrade.status","version":"1.4.0","connectorType":"appops","description":"Read durable status and exact deployment receipt for one AppOps self-upgrade operation identifier.","riskClass":"R1","readOnly":true,"idempotent":true,"inputSchema":{"type":"object","additionalProperties":false,"properties":{"operation_id":{"type":"string","pattern":"^[0-9a-f]{64}$"}},"required":["operation_id"]},"outputSchema":{"type":"object"},"limits":{"timeoutMs":30000,"maxOutputBytes":262144,"maxItems":1},"redactionProfile":"logs","requiredScopes":["project:{project}:read"]}}
+470 -30
View File
@@ -13,9 +13,11 @@ import time
from collections.abc import Callable
from datetime import UTC, datetime
from threading import Lock
from typing import Any
from typing import Any, Literal
from urllib.parse import quote, unquote, urlparse
import yaml
from packages.connector_kit import (
ConnectorContext,
GiteaClient,
@@ -72,6 +74,208 @@ _REPOSITORY_CONTEXT_DOCUMENT_LIMIT = 4
_REPOSITORY_CONTEXT_DOCUMENT_BYTES = 4_096
_REPOSITORY_CONTEXT_DOCUMENT_TOTAL_BYTES = 8_192
_PROJECTBRAIN_MANAGED_VALIDATION_REPOSITORY = "jens/projectbrain"
_PROJECTBRAIN_QUALITY_VALIDATION_CONTEXT = "Pull request / quality (pull_request)"
_PROJECTBRAIN_REQUIRED_VALIDATION_CONTEXTS = (
MANAGED_VALIDATION_CONTEXT,
_PROJECTBRAIN_QUALITY_VALIDATION_CONTEXT,
)
_PROJECTBRAIN_REQUIRED_PROTECTION_LOCKS = {
"enable_push": False,
"enable_push_whitelist": False,
"enable_force_push": False,
"enable_force_push_allowlist": False,
"enable_bypass_allowlist": False,
"enable_status_check": True,
"block_admin_merge_override": True,
}
# Original source commit for the ProjectBrain-specific profile lineage.
_PROJECTBRAIN_VALIDATION_PROFILE_SOURCE_SHA = "57157b1b24d3679018b54970c934fc35d1735df7"
# SHA-256 of JSON-dumped jobs.full (sort_keys, compact separators), parsed with
# BaseLoader. It pins the reviewed ProjectBrain workflow copy below.
# Frozen workflow source and copied fixture raw SHA-256:
# 2f78d37ff51f38f0a3b0c9e222eb2dbf83d5108aee3615eb53f2c01283f37f34.
# Refresh only after review.
_PROJECTBRAIN_VALIDATION_FULL_JOB_DIGEST = (
"a3fefb8865f0bd0d4a8c10af79eeb5e22367c5e82e9333a2fe04bdaf97d0193b"
)
_PROJECTBRAIN_VALIDATION_MAX_YAML_CHARS = 200_000
_PROJECTBRAIN_VALIDATION_MAX_YAML_TOKENS = 10_000
_PROJECTBRAIN_VALIDATION_MAX_YAML_DEPTH = 64
_PROJECTBRAIN_VALIDATION_RUN_NAME = "Managed validation / ${{ inputs.profile || 'full' }}"
_PROJECTBRAIN_VALIDATION_DISPATCH_OPTIONS = [
"test",
"lint",
"typecheck",
"build",
"security",
"full",
]
_PROJECTBRAIN_VALIDATION_TOP_LEVEL_KEYS = frozenset(
{"name", "run-name", "on", "permissions", "concurrency", "jobs"}
)
_PROJECTBRAIN_VALIDATION_CONCURRENCY = {
"group": "managed-validation-${{ gitea.repository }}-${{ gitea.ref }}",
"cancel-in-progress": "true",
}
class _UniqueWorkflowLoader(yaml.BaseLoader):
"""Parse workflow YAML without YAML 1.1's special treatment of the `on` key."""
def _construct_unique_workflow_mapping(loader: Any, node: Any) -> dict[str, Any]:
mapping: dict[str, Any] = {}
for key_node, value_node in node.value:
key = loader.construct_object(key_node)
if not isinstance(key, str) or key in mapping:
raise yaml.constructor.ConstructorError(
"while constructing a workflow mapping",
node.start_mark,
"workflow mapping keys must be unique strings",
key_node.start_mark,
)
mapping[key] = loader.construct_object(value_node)
return mapping
_UniqueWorkflowLoader.add_constructor(
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
_construct_unique_workflow_mapping,
)
def _workflow_mapping(value: Any) -> dict[str, Any] | None:
return value if isinstance(value, dict) else None
def _workflow_yaml_is_bounded_and_alias_free(workflow_content: str) -> bool:
if len(workflow_content) > _PROJECTBRAIN_VALIDATION_MAX_YAML_CHARS:
return False
container_starts = (
yaml.tokens.BlockMappingStartToken,
yaml.tokens.BlockSequenceStartToken,
yaml.tokens.FlowMappingStartToken,
yaml.tokens.FlowSequenceStartToken,
)
container_ends = (
yaml.tokens.BlockEndToken,
yaml.tokens.FlowMappingEndToken,
yaml.tokens.FlowSequenceEndToken,
)
depth = 0
try:
for token_count, token in enumerate(
yaml.scan(workflow_content, Loader=_UniqueWorkflowLoader), start=1
):
if token_count > _PROJECTBRAIN_VALIDATION_MAX_YAML_TOKENS:
return False
if isinstance(token, yaml.tokens.AnchorToken | yaml.tokens.AliasToken):
return False
if isinstance(token, container_starts):
depth += 1
if depth > _PROJECTBRAIN_VALIDATION_MAX_YAML_DEPTH:
return False
elif isinstance(token, container_ends):
depth -= 1
if depth < 0:
return False
except (yaml.YAMLError, RecursionError, TypeError, ValueError):
return False
return depth == 0
def _projectbrain_managed_validation_is_strong(workflow_content: str) -> bool:
if not _workflow_yaml_is_bounded_and_alias_free(workflow_content):
return False
try:
# BaseLoader constructs only strings/lists/maps; it never creates Python objects.
workflow = yaml.load(workflow_content, Loader=_UniqueWorkflowLoader) # noqa: S506
except (yaml.YAMLError, RecursionError, TypeError, ValueError):
return False
document = _workflow_mapping(workflow)
if document is None or document.get("name") != "Managed validation":
return False
if (
not set(document).issubset(_PROJECTBRAIN_VALIDATION_TOP_LEVEL_KEYS)
or document.get("run-name") != _PROJECTBRAIN_VALIDATION_RUN_NAME
or document.get("permissions") != {"contents": "read"}
or document.get("concurrency") != _PROJECTBRAIN_VALIDATION_CONCURRENCY
):
return False
triggers = _workflow_mapping(document.get("on"))
if (
triggers is None
or set(triggers) != {"push", "pull_request", "workflow_dispatch"}
):
return False
dispatch = _workflow_mapping(triggers.get("workflow_dispatch"))
dispatch_inputs = _workflow_mapping(dispatch.get("inputs")) if dispatch is not None else None
profile_input = (
_workflow_mapping(dispatch_inputs.get("profile"))
if dispatch_inputs is not None
else None
)
if (
dispatch_inputs is None
or set(dispatch) != {"inputs"}
or set(dispatch_inputs) != {"profile"}
or profile_input is None
or set(profile_input) != {"description", "required", "default", "type", "options"}
or not isinstance(profile_input.get("description"), str)
or profile_input.get("required") != "true"
or profile_input.get("default") != "full"
or profile_input.get("type") != "choice"
or profile_input.get("options") != _PROJECTBRAIN_VALIDATION_DISPATCH_OPTIONS
):
return False
push = _workflow_mapping(triggers.get("push"))
if (
push is None
or set(push) != {"branches"}
or push.get("branches") != ["main"]
):
return False
pull_request = triggers.get("pull_request")
if pull_request not in (None, ""):
pull_request_mapping = _workflow_mapping(pull_request)
if pull_request_mapping is None:
return False
if set(pull_request_mapping) - {"branches"}:
return False
branches = pull_request_mapping.get("branches")
if branches is not None and branches != ["main"]:
return False
jobs = _workflow_mapping(document.get("jobs"))
if jobs is None or set(jobs) != {"full"}:
return False
full_job = _workflow_mapping(jobs.get("full"))
if full_job is None:
return False
try:
full_job_digest = hashlib.sha256(
json.dumps(full_job, sort_keys=True, separators=(",", ":")).encode("utf-8")
).hexdigest()
except (RecursionError, TypeError, ValueError):
return False
return full_job_digest == _PROJECTBRAIN_VALIDATION_FULL_JOB_DIGEST
def _managed_validation_profile(
repository_qualified_name: str, workflow_content: str | None
) -> str | None:
"""Accept only the canonical workflow, except for ProjectBrain's stronger bounded profile."""
if workflow_content is None:
return None
if repository_qualified_name.casefold() == _PROJECTBRAIN_MANAGED_VALIDATION_REPOSITORY:
if _projectbrain_managed_validation_is_strong(workflow_content):
return "projectbrain"
return None
digest = hashlib.sha256(workflow_content.encode("utf-8")).hexdigest()
return "canonical" if digest == MANAGED_VALIDATION_DIGEST else None
def _select_validation_workflow(workflows: list[dict[str, Any]]) -> dict[str, Any] | None:
"""Prefer the bounded managed contract over legacy validation workflows."""
@@ -177,6 +381,19 @@ def _branch_protection_projection(payload: dict[str, Any] | None) -> dict[str, A
return {key: payload.get(key) for key in desired_keys}
def _branch_protection_effective_projection(
payload: dict[str, Any] | None,
) -> dict[str, Any] | None:
if payload is None:
return None
volatile_metadata = {"id", "created_at", "updated_at"}
return {
key: value
for key, value in payload.items()
if key not in volatile_metadata and key != "status_check_contexts"
}
def _validate_ref(value: str, *, change_branch: bool = False) -> str:
pattern = _SAFE_CHANGE_BRANCH if change_branch else _SAFE_REF
if (
@@ -800,14 +1017,19 @@ class GiteaConnector:
None,
)
desired = _desired_branch_protection(default_branch or "main")
if current is not None:
existing_contexts = current.get("status_check_contexts")
if isinstance(existing_contexts, list):
required_contexts = (
_PROJECTBRAIN_REQUIRED_VALIDATION_CONTEXTS
if repository.qualified_name.casefold() == _PROJECTBRAIN_MANAGED_VALIDATION_REPOSITORY
else (MANAGED_VALIDATION_CONTEXT,)
)
existing_contexts = current.get("status_check_contexts") if current is not None else None
preserved_contexts = (
{str(item) for item in existing_contexts if str(item).strip()}
if isinstance(existing_contexts, list)
else set()
)
desired["status_check_contexts"] = sorted(
{
MANAGED_VALIDATION_CONTEXT,
*(str(item) for item in existing_contexts if str(item).strip()),
}
set(required_contexts).union(preserved_contexts)
)
current_projection = _branch_protection_projection(current)
protection_diff = {
@@ -839,13 +1061,51 @@ class GiteaConnector:
if workflow_content is not None
else None
)
workflow_profile = _managed_validation_profile(
repository.qualified_name, workflow_content
)
is_projectbrain = (
repository.qualified_name.casefold()
== _PROJECTBRAIN_MANAGED_VALIDATION_REPOSITORY
)
if is_projectbrain and workflow_profile is None:
blockers.append("managed-validation-projectbrain-profile-invalid")
# Fail closed: governance must never replace ProjectBrain's
# repository-specific database/E2E gate with the generic baseline.
workflow_action = "none"
desired_workflow_digest = None
else:
workflow_action = (
"create"
if workflow_content is None
else "none"
if workflow_digest == MANAGED_VALIDATION_DIGEST
if workflow_profile is not None
else "update"
)
desired_workflow_digest = (
workflow_digest
if workflow_profile == "projectbrain"
else MANAGED_VALIDATION_DIGEST
)
if is_projectbrain and current_projection is not None:
if any(
current_projection.get(key) != expected
for key, expected in _PROJECTBRAIN_REQUIRED_PROTECTION_LOCKS.items()
):
blockers.append("managed-validation-projectbrain-protection-safety-locks-invalid")
if workflow_profile == "projectbrain":
desired = dict(current_projection)
desired["status_check_contexts"] = sorted(
set(required_contexts).union(preserved_contexts)
)
protection_diff = {
key: {
"current": None if current_projection is None else current_projection.get(key),
"desired": value,
}
for key, value in desired.items()
if current_projection is None or current_projection.get(key) != value
}
protection_action = "create" if current is None else "update" if protection_diff else "none"
plan_payload = {
"repositoryRef": repository_ref,
@@ -853,7 +1113,9 @@ class GiteaConnector:
"workflow": {
"path": MANAGED_VALIDATION_PATH,
"currentDigest": workflow_digest,
"desiredDigest": MANAGED_VALIDATION_DIGEST,
"desiredDigest": desired_workflow_digest,
"profile": workflow_profile
or ("projectbrain-required" if is_projectbrain else "canonical"),
"action": workflow_action,
},
"branchProtection": {
@@ -864,17 +1126,50 @@ class GiteaConnector:
},
"blockers": blockers,
}
effective_protection = _branch_protection_effective_projection(current)
plan_hash_source: dict[str, Any] = plan_payload
if is_projectbrain:
plan_hash_source = {
"plan": plan_payload,
"effectiveProtection": effective_protection,
}
plan_hash = hashlib.sha256(
json.dumps(plan_payload, sort_keys=True, separators=(",", ":")).encode()
json.dumps(plan_hash_source, sort_keys=True, separators=(",", ":")).encode()
).hexdigest()
protection_only_available = (
is_projectbrain
and current is not None
and current.get("enable_status_check") is True
and default_branch == "main"
and isinstance(desired.get("status_check_contexts"), list)
and set(blockers) == {"managed-validation-projectbrain-profile-invalid"}
)
protection_only_plan_hash = (
hashlib.sha256(
json.dumps(
{
"applyScope": "protection_only",
"plan": plan_payload,
"effectiveProtection": effective_protection,
},
sort_keys=True,
separators=(",", ":"),
).encode()
).hexdigest()
if protection_only_available
else None
)
return {
**plan_payload,
"planHash": plan_hash,
"protectionOnlyPlanHash": protection_only_plan_hash,
"protectionOnlyAvailable": protection_only_available,
"compliant": not blockers and workflow_action == "none" and protection_action == "none",
"_repository": repository,
"_workflowContent": workflow_content,
"_workflowSha": workflow_sha,
"_currentProtection": current,
"_protectionOnlyAvailable": protection_only_available,
}
@governed_tool("gitea.branchprotection.get")
@@ -946,17 +1241,103 @@ class GiteaConnector:
@governed_tool("gitea.branchprotection.ensure")
def branch_protection_ensure(
self, context: ConnectorContext, repository_ref: str, expected_plan_hash: str
self,
context: ConnectorContext,
repository_ref: str,
expected_plan_hash: str,
apply_scope: Literal["all", "protection_only"] = "all",
) -> dict[str, Any]:
if apply_scope not in {"all", "protection_only"}:
raise GiteaError("GITEA_INPUT_INVALID", "governance apply scope is not allowlisted")
plan = self._governance_plan_data(context, repository_ref)
expected_hash = (
plan["planHash"]
if apply_scope == "all"
else plan["protectionOnlyPlanHash"]
)
if (
not re.fullmatch(r"[0-9a-f]{64}", expected_plan_hash)
or expected_plan_hash != plan["planHash"]
not isinstance(expected_hash, str)
or not re.fullmatch(r"[0-9a-f]{64}", expected_plan_hash)
or expected_plan_hash != expected_hash
):
raise GiteaError(
"GITEA_PLAN_STALE",
"governance state changed; preview the repository again before applying",
)
if apply_scope == "protection_only":
allowed_blockers = {"managed-validation-projectbrain-profile-invalid"}
if (
not plan["_protectionOnlyAvailable"]
or any(blocker not in allowed_blockers for blocker in plan["blockers"])
):
raise GiteaError(
"GITEA_GOVERNANCE_BLOCKED",
"ProjectBrain protection-only apply requires an existing "
"enabled status-check rule",
)
current_protection = plan["_currentProtection"]
if current_protection is None:
raise GiteaError(
"GITEA_GOVERNANCE_BLOCKED",
"ProjectBrain protection-only apply requires an existing "
"enabled status-check rule",
)
current_contexts = current_protection.get("status_check_contexts")
expected_contexts = list(plan["branchProtection"]["desired"]["status_check_contexts"])
protection_changed = current_contexts != expected_contexts
if not protection_changed:
return self._envelope(
context,
{
**self._public_governance_plan(plan),
"applyScope": apply_scope,
"changed": False,
"verified": True,
"temporaryPushAllowlistUsed": False,
},
source=f"gitea:{repository_ref}",
)
self._writer().update_branch_protection(
plan["_repository"],
str(plan["defaultBranch"]),
{"status_check_contexts": expected_contexts},
)
verified_plan = self._governance_plan_data(context, repository_ref)
verified_protection = verified_plan["branchProtection"]["current"]
expected_effective_protection = _branch_protection_effective_projection(
current_protection
)
actual_effective_protection = _branch_protection_effective_projection(
verified_plan["_currentProtection"]
)
actual_contexts = set(
verified_protection.get("status_check_contexts") or []
) if verified_protection is not None else set()
if (
verified_protection is None
or verified_protection.get("enable_status_check") is not True
or actual_contexts != set(expected_contexts)
or actual_effective_protection != expected_effective_protection
or verified_plan["workflow"]["currentDigest"]
!= plan["workflow"]["currentDigest"]
):
raise GiteaError(
"GITEA_GOVERNANCE_VERIFY_FAILED",
"Gitea did not retain the requested ProjectBrain protection-only contract",
)
return self._envelope(
context,
{
**self._public_governance_plan(verified_plan),
"applyScope": apply_scope,
"changed": True,
"verified": True,
"temporaryPushAllowlistUsed": False,
},
source=f"gitea:{repository_ref}",
)
if plan["blockers"]:
raise GiteaError(
"GITEA_GOVERNANCE_BLOCKED",
@@ -965,7 +1346,12 @@ class GiteaConnector:
if plan["compliant"]:
return self._envelope(
context,
{**self._public_governance_plan(plan), "changed": False, "verified": True},
{
**self._public_governance_plan(plan),
"applyScope": apply_scope,
"changed": False,
"verified": True,
},
source=f"gitea:{repository_ref}",
)
@@ -992,6 +1378,11 @@ class GiteaConnector:
if protection_action == "create":
writer.create_branch_protection(repository, desired)
elif protection_action == "update":
if plan["workflow"]["profile"] == "projectbrain":
edit_payload = {
"status_check_contexts": desired["status_check_contexts"]
}
else:
edit_payload = {
key: value
for key, value in desired.items()
@@ -1027,7 +1418,36 @@ class GiteaConnector:
raise
verified_plan = self._governance_plan_data(context, repository_ref)
if not verified_plan["compliant"]:
is_projectbrain_profile = plan["workflow"]["profile"] == "projectbrain"
verified_contexts = set(
(verified_plan["branchProtection"]["current"] or {}).get(
"status_check_contexts"
)
or []
)
expected_contexts = set(
plan["branchProtection"]["desired"].get("status_check_contexts") or []
)
projectbrain_protection_changed = is_projectbrain_profile and (
verified_contexts != expected_contexts
or (
plan["_currentProtection"] is not None
and _branch_protection_effective_projection(
verified_plan["_currentProtection"]
)
!= _branch_protection_effective_projection(plan["_currentProtection"])
)
)
projectbrain_workflow_changed = (
is_projectbrain_profile
and verified_plan["workflow"]["currentDigest"]
!= plan["workflow"]["currentDigest"]
)
if (
not verified_plan["compliant"]
or projectbrain_protection_changed
or projectbrain_workflow_changed
):
raise GiteaError(
"GITEA_GOVERNANCE_VERIFY_FAILED",
"Gitea did not retain the requested governance contract",
@@ -1036,6 +1456,7 @@ class GiteaConnector:
context,
{
**self._public_governance_plan(verified_plan),
"applyScope": apply_scope,
"changed": True,
"verified": True,
"temporaryPushAllowlistUsed": temporary_push_allowlist_used,
@@ -1100,8 +1521,12 @@ class GiteaConnector:
for item in plans:
repository_ref = str(item.get("repositoryRef", ""))
try:
apply_scope = str(item.get("applyScope", "all"))
result = self.branch_protection_ensure(
context, repository_ref, str(item.get("planHash", ""))
context,
repository_ref,
str(item.get("planHash", "")),
apply_scope=apply_scope, # type: ignore[arg-type]
)["data"]
items.append(
{
@@ -1109,6 +1534,7 @@ class GiteaConnector:
"applied": True,
"changed": bool(result.get("changed")),
"verified": bool(result.get("verified")),
"applyScope": str(result.get("applyScope", "all")),
}
)
except GiteaError as exc:
@@ -4519,13 +4945,13 @@ GITEA_READ_TOOLS = tuple(
"gitea.change.status",
"gitea.change.review",
"gitea.workspace.status",
"gitea.branchprotection.preview",
"gitea.portfolio.governance.preview",
"gitea.portfolio.governance.verify",
}
else (
"1.8.0"
if name.startswith("gitea.branchprotection.")
or name.startswith("gitea.portfolio.governance.")
else "1.5.0"
),
else "1.8.0"
if name == "gitea.branchprotection.get"
else "1.5.0",
connector_type="gitea",
risk_class="R1",
input_schema=_tool_input_schema(name),
@@ -4620,6 +5046,11 @@ def _write_tool_input_schema(name: str) -> dict[str, Any]:
}
if name == "gitea.branchprotection.ensure":
properties["expectedPlanHash"] = {"type": "string", "pattern": "^[0-9a-f]{64}$"}
properties["applyScope"] = {
"type": "string",
"enum": ["all", "protection_only"],
"default": "all",
}
required.append("expectedPlanHash")
elif name == "gitea.portfolio.governance.apply":
return {
@@ -4639,7 +5070,19 @@ def _write_tool_input_schema(name: str) -> dict[str, Any]:
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]{0,127}$",
},
"planHash": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
"planHash": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": (
"Use planHash for all scope or protectionOnlyPlanHash "
"for protection_only scope."
),
},
"applyScope": {
"type": "string",
"enum": ["all", "protection_only"],
"default": "all",
},
},
"required": ["repositoryRef", "planHash"],
},
@@ -4827,13 +5270,10 @@ GITEA_PR_WRITE_TOOLS = tuple(
"gitea.pullrequest.update",
"gitea.changeset.apply",
}
else "1.9.0"
if name in {"gitea.branchprotection.ensure", "gitea.portfolio.governance.apply"}
else "1.8.0"
if name
in {
"gitea.repository.create",
"gitea.branchprotection.ensure",
"gitea.portfolio.governance.apply",
}
if name == "gitea.repository.create"
else "1.5.0"
),
connector_type="gitea",
+50 -1
View File
@@ -9,6 +9,49 @@ from uuid import UUID, uuid4
import httpx
APPOPS_INPUT_SCHEMAS: dict[str, dict[str, Any]] = {
"server.appops.deployment.status": {
"type": "object",
"additionalProperties": False,
},
"server.appops.self_upgrade.preflight": {
"type": "object",
"additionalProperties": False,
"properties": {
"repository": {"type": "string", "const": "jens/unraid-appops-gateway"},
"revision": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
"reason": {"type": "string", "minLength": 8, "maxLength": 500},
"expected_current_revision": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
},
"required": ["repository", "revision", "reason"],
},
"server.appops.self_upgrade.apply": {
"type": "object",
"additionalProperties": False,
"properties": {
"repository": {"type": "string", "const": "jens/unraid-appops-gateway"},
"revision": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
"reason": {"type": "string", "minLength": 8, "maxLength": 500},
"expected_preflight_fingerprint": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
"idempotency_key": {"type": "string", "pattern": "^[A-Za-z0-9._:-]{8,128}$"},
"expected_current_revision": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
},
"required": [
"repository",
"revision",
"reason",
"expected_preflight_fingerprint",
"idempotency_key",
],
},
"server.appops.self_upgrade.status": {
"type": "object",
"additionalProperties": False,
"properties": {"operation_id": {"type": "string", "pattern": "^[0-9a-f]{64}$"}},
"required": ["operation_id"],
},
}
class FakeContextForge:
"""Fake REST gateway for offline adapter and reconciliation tests."""
@@ -201,6 +244,10 @@ class FakeContextForge:
"server.deployment.status",
"server.deployment.verify",
"server.deployment.rollback",
"server.appops.deployment.status",
"server.appops.self_upgrade.preflight",
"server.appops.self_upgrade.apply",
"server.appops.self_upgrade.status",
),
"itworx-opencode": (
"opencode.capabilities.get",
@@ -251,7 +298,9 @@ class FakeContextForge:
@staticmethod
def _tool_payload(tool_name: str) -> dict[str, Any]:
payload: dict[str, Any] = {"id": str(uuid4()), "name": tool_name}
if tool_name == "gitea.repository.create":
if tool_name in APPOPS_INPUT_SCHEMAS:
payload["inputSchema"] = deepcopy(APPOPS_INPUT_SCHEMAS[tool_name])
elif tool_name == "gitea.repository.create":
payload["inputSchema"] = {
"type": "object",
"properties": {
+4
View File
@@ -45,6 +45,10 @@ UPSTREAM_TO_PUBLIC = {
"deployment.status": "server.deployment.status",
"deployment.verify": "server.deployment.verify",
"deployment.rollback": "server.deployment.rollback",
"appops.deployment.status": "server.appops.deployment.status",
"appops.self_upgrade.preflight": "server.appops.self_upgrade.preflight",
"appops.self_upgrade.apply": "server.appops.self_upgrade.apply",
"appops.self_upgrade.status": "server.appops.self_upgrade.status",
}
PUBLIC_TO_UPSTREAM = {value: key for key, value in UPSTREAM_TO_PUBLIC.items()}
MAX_BODY_BYTES = 1024 * 1024
+21 -5
View File
@@ -3,7 +3,7 @@ from __future__ import annotations
import os
import re
from collections.abc import Callable
from typing import Any, Literal, ParamSpec, TypeVar, cast
from typing import Annotated, Any, Literal, ParamSpec, TypeVar, cast
from mcp.server.mcpserver import MCPServer
from mcp.server.mcpserver.context import Context
@@ -87,6 +87,14 @@ class ResearchFetchOutput(BaseModel):
metadata: dict[str, Any] | None = None
class PortfolioGovernanceApplyPlan(BaseModel):
model_config = ConfigDict(extra="forbid")
repositoryRef: str
planHash: str = Field(pattern=r"^[0-9a-f]{64}$")
applyScope: Literal["all", "protection_only"] = "all"
def _repository_ref(name: str) -> str:
value = re.sub(r"[^a-z0-9]+", "-", name.casefold()).strip("-")
if not value:
@@ -655,9 +663,12 @@ def build_server() -> MCPServer[Any]:
repositoryRef: str,
expectedPlanHash: str,
context: Context[Any, Any],
applyScope: Literal["all", "protection_only"] = "all",
) -> dict[str, Any]:
"""Apply one unchanged governance preview and verify the stored result."""
return connector.branch_protection_ensure(current(context), repositoryRef, expectedPlanHash)
"""Apply one unchanged governance preview with its explicitly hashed scope."""
return connector.branch_protection_ensure(
current(context), repositoryRef, expectedPlanHash, apply_scope=applyScope
)
@write_tool(
name="gitea.portfolio.governance.apply",
@@ -665,10 +676,15 @@ def build_server() -> MCPServer[Any]:
structured_output=True,
)
async def portfolio_governance_apply(
plans: list[dict[str, str]], context: Context[Any, Any]
context: Context[Any, Any],
plans: Annotated[
list[PortfolioGovernanceApplyPlan], Field(min_length=1, max_length=5)
],
) -> dict[str, Any]:
"""Apply at most five exact governance plans; every item is independently verified."""
return connector.portfolio_governance_apply(current(context), plans)
return connector.portfolio_governance_apply(
current(context), [plan.model_dump() for plan in plans]
)
@write_tool(name="gitea.branch.create", annotations=SAFE_WRITE_TOOL, structured_output=True)
async def branch_create(
+210
View File
@@ -0,0 +1,210 @@
name: Managed validation
run-name: "Managed validation / ${{ inputs.profile || 'full' }}"
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
inputs:
profile:
description: Allowlisted validation profile
required: true
default: full
type: choice
options: [test, lint, typecheck, build, security, full]
permissions:
contents: read
concurrency:
group: managed-validation-${{ gitea.repository }}-${{ gitea.ref }}
cancel-in-progress: true
jobs:
full:
name: full
runs-on: linux-validation
timeout-minutes: 30
env:
CI: "true"
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: projectbrain_integration
POSTGRES_USER: projectbrain
POSTGRES_PASSWORD: projectbrain-test-password
options: >-
--health-cmd "pg_isready -U projectbrain -d projectbrain_integration"
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Validate repository with a bounded profile
shell: bash
env:
REQUESTED_PROFILE: ${{ inputs.profile }}
run: |
set -euo pipefail
profile="${REQUESTED_PROFILE:-full}"
case "${profile}" in
test|lint|typecheck|build|security|full) ;;
*) echo "Profile is not allowlisted" >&2; exit 2 ;;
esac
if [[ "${profile}" == security && ( ! -f package.json || ! -f pnpm-lock.yaml ) ]]; then
echo "Security profile requires the repository's pinned pnpm audit:security command" >&2
exit 2
fi
git diff --check
if git grep -nE '^(<<<<<<< |=======$|>>>>>>> )' -- . ':!*.lock' ':!*.patch'; then
echo "Unresolved merge markers detected" >&2
exit 1
fi
# MANAGED_FAST_PATH: only documentation may skip product tests.
# Include deletions and renames; workflow and runner changes must
# always execute validation.
if [[ -n "${GITHUB_BASE_REF:-}" ]]; then
git fetch --no-tags origin "+refs/heads/${GITHUB_BASE_REF}:refs/remotes/origin/${GITHUB_BASE_REF}"
managed_base="origin/${GITHUB_BASE_REF}"
git diff --check "${managed_base}..HEAD"
mapfile -t managed_changed_files < <(
git diff --name-only --no-renames "${managed_base}..HEAD"
)
managed_runtime_change=0
for managed_path in "${managed_changed_files[@]}"; do
case "${managed_path}" in
*.md|*.mdx)
;;
*)
managed_runtime_change=1
break
;;
esac
done
if [[ "${#managed_changed_files[@]}" -gt 0 && "${managed_runtime_change}" -eq 0 ]]; then
printf 'Managed validation fast path: %s non-runtime file(s); full product suite skipped.\n' \
"${#managed_changed_files[@]}"
if [[ "${profile}" == full ]]; then
node scripts/check-doc-links.mjs
node scripts/check-unraid-template-readiness.mjs
fi
exit 0
fi
fi
if [[ -f pyproject.toml || -f requirements.txt ]]; then
# Compile only tracked Python sources. Running compileall after a
# Node install would otherwise traverse node_modules and turn a
# lightweight baseline into a large runner workload.
git ls-files -z '*.py' | xargs -0 -r python -m py_compile
if [[ -f uv.lock ]]; then
python -m venv "${RUNNER_TEMP}/managed-uv"
uv_python="${RUNNER_TEMP}/managed-uv/bin/python"
"${uv_python}" -m pip install --disable-pip-version-check uv==0.10.0
managed_uv="${RUNNER_TEMP}/managed-uv/bin/uv"
export UV_PROJECT_ENVIRONMENT="${RUNNER_TEMP}/managed-project-venv"
"${managed_uv}" sync --locked
export PATH="${UV_PROJECT_ENVIRONMENT}/bin:${PATH}"
if [[ "${profile}" == test || "${profile}" == full ]]; then
if "${managed_uv}" run python -c 'import pytest' 2>/dev/null; then
"${managed_uv}" run python -m pytest
fi
fi
if [[ "${profile}" == lint || "${profile}" == full ]]; then
if "${managed_uv}" run python -c 'import ruff' 2>/dev/null; then
"${managed_uv}" run python -m ruff check .
fi
fi
elif [[ -f requirements.txt ]]; then
python -m venv "${RUNNER_TEMP}/managed-python"
managed_python="${RUNNER_TEMP}/managed-python/bin/python"
"${managed_python}" -m pip install --disable-pip-version-check -r requirements.txt
export PATH="${RUNNER_TEMP}/managed-python/bin:${PATH}"
if [[ "${profile}" == test || "${profile}" == full ]]; then
if "${managed_python}" -c 'import pytest' 2>/dev/null; then
"${managed_python}" -m pytest
fi
fi
fi
fi
# Keep ordinary unit/contract validation database-independent. The
# real PostgreSQL suite is enabled only after migrations are applied
# in the deployment-authoritative full profile below.
if [[ -f package.json ]]; then
corepack enable
if [[ -f pnpm-lock.yaml ]]; then
pnpm install --frozen-lockfile
if [[ "${profile}" == test ]]; then
pnpm --if-present test
[[ -f backend/package.json ]] && pnpm --filter projectbrain-api run test
elif [[ "${profile}" == full ]]; then
pnpm run test:coverage
[[ -f backend/package.json ]] && pnpm --filter projectbrain-api run test:coverage
fi
if [[ "${profile}" == security ]]; then
pnpm run audit:security
fi
if [[ "${profile}" == lint || "${profile}" == full ]]; then
pnpm --if-present lint
[[ -f backend/package.json ]] && pnpm --filter projectbrain-api run lint
fi
if [[ "${profile}" == typecheck || "${profile}" == full ]]; then
pnpm --if-present typecheck
[[ -f backend/package.json ]] && pnpm --filter projectbrain-api run typecheck
fi
if [[ "${profile}" == build || "${profile}" == full ]]; then
pnpm --if-present build
[[ -f backend/package.json ]] && pnpm --filter projectbrain-api run build
fi
if [[ "${profile}" == full ]]; then
pnpm run audit:openapi
pnpm run audit:architecture
pnpm run audit:docs
pnpm run audit:v1-freeze
pnpm run audit:unraid-template
fi
if [[ "${profile}" == full && -f backend/package.json ]]; then
export DATABASE_URL="postgresql://projectbrain:projectbrain-test-password@postgres:5432/projectbrain_integration?schema=public"
export TEST_DATABASE_URL="${DATABASE_URL}"
pnpm --dir backend prisma:generate
pnpm --dir backend db:deploy
pnpm --dir backend db:seed
pnpm --dir backend test:database
pnpm --dir backend exec prisma migrate reset --schema prisma/schema.prisma --force --skip-seed
pnpm --dir backend db:seed
fi
if [[ "${profile}" == full && -f playwright.config.ts ]]; then
pnpm exec playwright install --with-deps --force chromium chromium-headless-shell
pnpm test:e2e
pnpm test:e2e:real
fi
elif [[ -f package-lock.json ]]; then
npm ci
[[ "${profile}" == test || "${profile}" == full ]] && npm run --if-present test
[[ "${profile}" == lint || "${profile}" == full ]] && npm run --if-present lint
if [[ "${profile}" == typecheck || "${profile}" == full ]]; then
npm run --if-present typecheck
fi
[[ "${profile}" == build || "${profile}" == full ]] && npm run --if-present build
fi
fi
if [[ -f go.mod ]]; then
if [[ "${profile}" == test || "${profile}" == build || "${profile}" == full ]]; then
go test ./...
fi
fi
if [[ -f Cargo.toml ]]; then
if [[ "${profile}" == test || "${profile}" == build || "${profile}" == full ]]; then
cargo test --locked
fi
fi
if compgen -G '*.sln' >/dev/null; then
if [[ "${profile}" == test || "${profile}" == build || "${profile}" == full ]]; then
dotnet test --configuration Release
fi
fi
+95 -1
View File
@@ -18,7 +18,7 @@ ROOT = Path(__file__).resolve().parents[1]
def test_appops_tool_catalog_is_bijective_and_server_namespaced() -> None:
assert len(UPSTREAM_TO_PUBLIC) == len(PUBLIC_TO_UPSTREAM) == 25
assert len(UPSTREAM_TO_PUBLIC) == len(PUBLIC_TO_UPSTREAM) == 29
assert all(name.startswith("server.") for name in PUBLIC_TO_UPSTREAM)
assert PUBLIC_TO_UPSTREAM["server.summary.get"] == "get_system_summary"
assert PUBLIC_TO_UPSTREAM["server.operation.execute"] == "server.operation.execute"
@@ -28,6 +28,19 @@ def test_appops_tool_catalog_is_bijective_and_server_namespaced() -> None:
assert PUBLIC_TO_UPSTREAM["server.deployment.rollback"] == "deployment.rollback"
assert PUBLIC_TO_UPSTREAM["server.deployment.target.enroll"] == "target.enroll"
assert PUBLIC_TO_UPSTREAM["server.ci.runners.status"] == "gitea.actions.runners.status"
assert PUBLIC_TO_UPSTREAM["server.appops.deployment.status"] == "appops.deployment.status"
assert (
PUBLIC_TO_UPSTREAM["server.appops.self_upgrade.preflight"]
== "appops.self_upgrade.preflight"
)
assert (
PUBLIC_TO_UPSTREAM["server.appops.self_upgrade.apply"]
== "appops.self_upgrade.apply"
)
assert (
PUBLIC_TO_UPSTREAM["server.appops.self_upgrade.status"]
== "appops.self_upgrade.status"
)
def test_every_public_appops_tool_has_governance_metadata() -> None:
@@ -42,6 +55,50 @@ def test_every_public_appops_tool_has_governance_metadata() -> None:
assert set(PUBLIC_TO_UPSTREAM) <= canonical_names
def test_appops_self_upgrade_manifests_are_fixed_target_and_fingerprint_bound() -> None:
specs = {}
for path in (ROOT / "catalog" / "tools").glob("*.yaml"):
document = yaml.safe_load(path.read_text(encoding="utf-8"))
spec = document.get("spec") if isinstance(document, dict) else None
if isinstance(spec, dict) and spec.get("canonicalName", "").startswith(
"server.appops."
):
specs[spec["canonicalName"]] = spec
assert set(specs) == {
"server.appops.deployment.status",
"server.appops.self_upgrade.preflight",
"server.appops.self_upgrade.apply",
"server.appops.self_upgrade.status",
}
for name in (
"server.appops.self_upgrade.preflight",
"server.appops.self_upgrade.apply",
):
schema = specs[name]["inputSchema"]
assert schema["properties"]["repository"]["const"] == "jens/unraid-appops-gateway"
assert schema["properties"]["revision"]["pattern"] == "^[0-9a-f]{40}$"
assert schema["properties"]["reason"]["minLength"] == 8
assert schema["properties"]["reason"]["maxLength"] == 500
assert schema["properties"]["expected_current_revision"]["pattern"] == "^[0-9a-f]{40}$"
apply = specs["server.appops.self_upgrade.apply"]
assert apply["riskClass"] == "R3"
assert apply["readOnly"] is False
assert apply["inputSchema"]["properties"]["expected_preflight_fingerprint"][
"pattern"
] == "^[0-9a-f]{64}$"
assert {
"expected_preflight_fingerprint",
"idempotency_key",
} <= set(apply["inputSchema"]["required"])
policy = yaml.safe_load(
(ROOT / "catalog" / "policies" / "041-appops-governed-operations.yaml").read_text(
encoding="utf-8"
)
)
assert "server.appops.self_upgrade.apply" in policy["spec"]["match"]["toolPatterns"]
def test_appops_proxy_rewrites_only_registered_tool_calls() -> None:
request = {
"jsonrpc": "2.0",
@@ -73,6 +130,35 @@ def test_appops_proxy_rewrites_only_registered_tool_calls() -> None:
with pytest.raises(ValueError, match="not registered"):
rewrite_request({"method": "tools/call", "params": {"name": "shell.execute"}})
upgrade_request = {
"jsonrpc": "2.0",
"id": 10,
"method": "tools/call",
"params": {
"name": "server.appops.self_upgrade.apply",
"arguments": {
"repository": "jens/unraid-appops-gateway",
"revision": "a" * 40,
"reason": "approved release",
"expected_preflight_fingerprint": "b" * 64,
"idempotency_key": "upgrade-2026-09-30",
"expected_current_revision": "c" * 40,
},
},
}
upgrade_rewritten = rewrite_request(upgrade_request)
assert upgrade_rewritten["params"]["name"] == "appops.self_upgrade.apply"
assert upgrade_rewritten["params"]["arguments"] == upgrade_request["params"]["arguments"]
for upstream_name in (
"appops.deployment.status",
"appops.self_upgrade.preflight",
"appops.self_upgrade.apply",
"appops.self_upgrade.status",
):
with pytest.raises(ValueError, match="not registered"):
rewrite_request({"method": "tools/call", "params": {"name": upstream_name}})
def test_appops_proxy_rewrites_discovery_and_drops_unknown_tools() -> None:
response = {
@@ -84,6 +170,10 @@ def test_appops_proxy_rewrites_discovery_and_drops_unknown_tools() -> None:
{"name": "gitea.projects.catalog", "description": "projects"},
{"name": "deployment.preflight", "description": "deployment preflight"},
{"name": "deployment.apply", "description": "typed deployment"},
{"name": "appops.deployment.status", "description": "AppOps revision"},
{"name": "appops.self_upgrade.preflight", "description": "upgrade preflight"},
{"name": "appops.self_upgrade.apply", "description": "upgrade apply"},
{"name": "appops.self_upgrade.status", "description": "upgrade status"},
{"name": "future_unsafe_tool", "description": "unknown"},
]
},
@@ -93,6 +183,10 @@ def test_appops_proxy_rewrites_discovery_and_drops_unknown_tools() -> None:
{"name": "server.project.list", "description": "projects"},
{"name": "server.deployment.preflight", "description": "deployment preflight"},
{"name": "server.deployment.apply", "description": "typed deployment"},
{"name": "server.appops.deployment.status", "description": "AppOps revision"},
{"name": "server.appops.self_upgrade.preflight", "description": "upgrade preflight"},
{"name": "server.appops.self_upgrade.apply", "description": "upgrade apply"},
{"name": "server.appops.self_upgrade.status", "description": "upgrade status"},
]
+1 -1
View File
@@ -33,7 +33,7 @@ class CatalogLoaderTests(unittest.TestCase):
def test_committed_catalog_validates_without_network_or_secret_values(self) -> None:
report = CatalogLoader(ROOT / "catalog").load()
self.assertTrue(report.valid, report.issues)
self.assertEqual(len(report.documents), 277)
self.assertEqual(len(report.documents), 281)
self.assertEqual(len(report.issues), 0)
self.assertIsNotNone(report.desired_hash)
+713 -1
View File
@@ -24,7 +24,7 @@ from connectors.gitea.changesets import (
validate_operations,
)
from connectors.gitea.managed_validation import MANAGED_VALIDATION_WORKFLOW
from connectors.gitea.server import _select_validation_workflow
from connectors.gitea.server import _managed_validation_profile, _select_validation_workflow
from connectors.knowledge import KnowledgeConnector
from packages.connector_kit import (
ConnectorContext,
@@ -43,6 +43,49 @@ from services.control_api.gitea.registry import registration_from_project
PROJECT_ID = UUID("11111111-1111-1111-1111-111111111111")
OTHER_PROJECT_ID = UUID("22222222-2222-2222-2222-222222222222")
ROOT = Path(__file__).resolve().parents[1]
PROJECTBRAIN_VALIDATION_WORKFLOW = (
ROOT / "tests" / "fixtures" / "projectbrain-managed-validation.yml"
).read_text(encoding="utf-8")
def projectbrain_connector(
server: FakeGiteaServer | None = None,
) -> tuple[GiteaConnector, FakeGiteaServer]:
fixture = server or FakeGiteaServer()
fixture._repos["Jens/projectbrain"] = {
"owner": {"login": "Jens"},
"name": "projectbrain",
"full_name": "Jens/projectbrain",
"description": "ProjectBrain",
"private": True,
"default_branch": "main",
}
fixture._managed_workflows["Jens/projectbrain"] = PROJECTBRAIN_VALIDATION_WORKFLOW
protection = copy.deepcopy(fixture._branch_protections["acme/hub"][0])
protection["status_check_contexts"] = ["Pull request / quality (pull_request)"]
protection["required_approvals"] = 2
fixture._branch_protections["Jens/projectbrain"] = [protection]
settings = GiteaSettings(
base_url="http://gitea.test",
token_reference="docker-secret://gitea_mcp_token", # noqa: S106
allow_insecure_http_for_tests=True,
)
client = GiteaClient(
settings, StaticTokenProvider("fixture-token-must-not-leak"), transport=fixture.transport()
)
registry = GiteaRepositoryRegistry(
(
RegisteredRepository(
repository_ref="projectbrain-repository",
project_id=PROJECT_ID,
owner="Jens",
name="projectbrain",
source_uri="itworx-git://projectbrain/projectbrain-repository",
private=True,
),
)
)
return GiteaConnector(client, registry, write_client=client), fixture
def context(project_id: UUID = PROJECT_ID, slug: str = "hub") -> ConnectorContext:
@@ -114,6 +157,262 @@ class GiteaConnectorTests(unittest.TestCase):
self.assertIs(_select_validation_workflow([legacy, managed]), managed)
self.assertIs(_select_validation_workflow([managed, legacy]), managed)
def test_projectbrain_managed_validation_profile_is_pinned_and_repository_scoped(self) -> None:
self.assertEqual(
_managed_validation_profile("Jens/projectbrain", PROJECTBRAIN_VALIDATION_WORKFLOW),
"projectbrain",
)
self.assertIsNone(
_managed_validation_profile(
"Jens/projectbrain",
PROJECTBRAIN_VALIDATION_WORKFLOW.replace("pnpm test:e2e:real", ""),
)
)
self.assertIsNone(
_managed_validation_profile(
"Jens/projectbrain",
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
"pnpm test:e2e:real", "# pnpm test:e2e:real"
),
)
)
self.assertIsNone(
_managed_validation_profile(
"Jens/projectbrain",
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
"pnpm test:e2e:real", 'echo "pnpm test:e2e:real"'
),
)
)
self.assertIsNone(
_managed_validation_profile(
"Jens/projectbrain",
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
'export TEST_DATABASE_URL="${DATABASE_URL}"',
'# export TEST_DATABASE_URL="${DATABASE_URL}"',
),
)
)
self.assertIsNone(
_managed_validation_profile(
"Jens/projectbrain",
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" image: postgres:16", " image: postgres:15"
),
)
)
self.assertIsNone(
_managed_validation_profile(
"Jens/projectbrain",
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(" pull_request:\n", ""),
)
)
self.assertIsNone(
_managed_validation_profile(
"Jens/projectbrain",
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" pull_request:\n", ' pull_request:\n paths: ["backend/**"]\n'
),
)
)
self.assertIsNone(
_managed_validation_profile(
"Jens/projectbrain",
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" pull_request:\n", " pull_request:\n types: [edited]\n"
),
)
)
self.assertIsNone(
_managed_validation_profile(
"Jens/projectbrain",
PROJECTBRAIN_VALIDATION_WORKFLOW.replace("branches: [main]", "branches: [master]"),
)
)
self.assertIsNone(
_managed_validation_profile(
"Jens/projectbrain",
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
"branches: [main]", "branches: [main, '!main']"
),
)
)
self.assertIsNone(
_managed_validation_profile(
"Jens/projectbrain",
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" pull_request:\n",
" pull_request:\n branches: [main, '!main']\n",
),
)
)
self.assertIsNone(
_managed_validation_profile(
"Jens/projectbrain",
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" name: full\n", " name: full\n if: false\n"
),
)
)
self.assertIsNone(
_managed_validation_profile(
"Jens/projectbrain",
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" name: full\n", " name: full\n needs: [conditional-setup]\n"
),
)
)
self.assertIsNone(
_managed_validation_profile(
"Jens/projectbrain",
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" - name: Validate repository with a bounded profile\n",
" - name: Validate repository with a bounded profile\n"
" if: false\n",
),
)
)
self.assertIsNone(
_managed_validation_profile(
"Jens/projectbrain",
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" name: full\n", " name: full\n continue-on-error: true\n"
),
)
)
self.assertIsNone(
_managed_validation_profile(
"Jens/projectbrain",
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" - name: Validate repository with a bounded profile\n",
" - name: Validate repository with a bounded profile\n"
" continue-on-error: true\n",
),
)
)
bypass_mutations = (
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" pnpm test:e2e:real\n",
" cat <<'EOF'\n"
" pnpm test:e2e:real\n"
" EOF\n",
),
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" pnpm --dir backend test:database\n",
" exit 0\n"
" pnpm --dir backend test:database\n",
),
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" pnpm test:e2e:real\n",
" if false; then\n"
" pnpm test:e2e:real\n"
" fi\n",
),
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" pnpm test:e2e:real\n",
" export PATH=/tmp/untrusted:$PATH\n"
" pnpm test:e2e:real\n",
),
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 "
"# v4.2.2\n",
" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 "
"# v4.2.2\n"
" - run: echo injected-action\n",
),
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
"run-name: \"Managed validation / ${{ inputs.profile || 'full' }}\"",
'run-name: "Managed validation / full"',
),
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" default: full\n", " default: test\n"
),
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
"permissions:\n", "env:\n PATH: /tmp/untrusted\n\npermissions:\n"
),
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
"permissions:\n", "defaults:\n run:\n shell: sh\n\npermissions:\n"
),
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
"jobs:\n", "jobs:\n extra:\n runs-on: linux-validation\n steps: []\n"
),
)
for modified_workflow in bypass_mutations:
with self.subTest(modified_workflow=modified_workflow[-160:]):
self.assertIsNone(
_managed_validation_profile("Jens/projectbrain", modified_workflow)
)
alias_chain = ["level0: &level0 [marker]"]
for level in range(1, 23):
alias_chain.append(
f"level{level}: &level{level} [*level{level - 1}, *level{level - 1}]"
)
alias_bomb = PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
"jobs:\n", "\n".join(alias_chain) + "\njobs:\n"
)
self.assertIsNone(_managed_validation_profile("Jens/projectbrain", alias_bomb))
self.assertIsNone(
_managed_validation_profile(
"Jens/projectbrain", PROJECTBRAIN_VALIDATION_WORKFLOW + "\njobs: {}\n"
)
)
self.assertIsNone(
_managed_validation_profile("Acme/hub", PROJECTBRAIN_VALIDATION_WORKFLOW)
)
self.assertEqual(
_managed_validation_profile("Acme/hub", MANAGED_VALIDATION_WORKFLOW),
"canonical",
)
self.assertIsNone(
_managed_validation_profile(
"Acme/hub", MANAGED_VALIDATION_WORKFLOW + "\n# custom project workflow\n"
)
)
def test_projectbrain_profile_pins_coverage_audits_security_and_doc_checks(self) -> None:
required_commands = (
("root coverage gate", "pnpm run test:coverage", "pnpm --if-present test"),
(
"backend coverage gate",
"pnpm --filter projectbrain-api run test:coverage",
"pnpm --filter projectbrain-api run test",
),
("security audit", "pnpm run audit:security", "pnpm --if-present audit:security"),
("OpenAPI audit", "pnpm run audit:openapi", "echo skipped-openapi-audit"),
(
"architecture audit",
"pnpm run audit:architecture",
"echo skipped-architecture-audit",
),
("documentation audit", "pnpm run audit:docs", "echo skipped-docs-audit"),
("v1 freeze audit", "pnpm run audit:v1-freeze", "echo skipped-v1-freeze-audit"),
(
"Unraid template audit",
"pnpm run audit:unraid-template",
"echo skipped-unraid-template-audit",
),
(
"documentation fast-path check",
"node scripts/check-doc-links.mjs",
"echo skipped-doc-link-check",
),
(
"Unraid documentation fast-path check",
"node scripts/check-unraid-template-readiness.mjs",
"echo skipped-unraid-readiness-check",
),
)
for label, command, replacement in required_commands:
with self.subTest(gate=label):
self.assertIn(command, PROJECTBRAIN_VALIDATION_WORKFLOW)
modified_workflow = PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
command, replacement, 1
)
self.assertIsNone(
_managed_validation_profile("Jens/projectbrain", modified_workflow)
)
def test_native_skills_are_complete_digest_verified_and_bounded(self) -> None:
from services.runtime.mcp_skills import SkillGetParams, SkillListParams, StaticSkills
@@ -653,6 +952,16 @@ class GiteaConnectorTests(unittest.TestCase):
"main",
)
self.assertIn("expectedPlanHash", schemas["gitea.branchprotection.ensure"]["required"])
self.assertEqual(
schemas["gitea.branchprotection.ensure"]["properties"]["applyScope"]["enum"],
["all", "protection_only"],
)
portfolio_plan_properties = schemas["gitea.portfolio.governance.apply"]["properties"][
"plans"
]["items"]["properties"]
self.assertEqual(
portfolio_plan_properties["applyScope"]["enum"], ["all", "protection_only"]
)
self.assertEqual(
schemas["gitea.portfolio.governance.apply"]["properties"]["plans"]["maxItems"],
5,
@@ -755,6 +1064,24 @@ class GiteaConnectorTests(unittest.TestCase):
self.assertEqual(server.version, "1.14.0")
self.assertIn("plan-locked governance tools", server.instructions)
names = {tool.name for tool in tools}
ensure_tool = next(
tool for tool in tools if tool.name == "gitea.branchprotection.ensure"
)
portfolio_apply_tool = next(
tool for tool in tools if tool.name == "gitea.portfolio.governance.apply"
)
self.assertEqual(
ensure_tool.input_schema["properties"]["applyScope"]["enum"],
["all", "protection_only"],
)
portfolio_schema = portfolio_apply_tool.input_schema
plan_ref = portfolio_schema["properties"]["plans"]["items"]["$ref"]
plan_schema_name = plan_ref.rsplit("/", maxsplit=1)[-1]
plan_schema = portfolio_schema["$defs"][plan_schema_name]
self.assertEqual(
plan_schema["properties"]["applyScope"]["enum"], ["all", "protection_only"]
)
self.assertEqual(portfolio_schema["properties"]["plans"]["maxItems"], 5)
self.assertTrue(
{
"gitea.repository.create",
@@ -1065,6 +1392,391 @@ class GiteaConnectorTests(unittest.TestCase):
finally:
connector_instance.client.close()
def test_projectbrain_custom_workflow_is_preserved_while_full_and_quality_are_added(
self,
) -> None:
connector_instance, fixture = projectbrain_connector()
original_workflow = fixture._managed_workflows["Jens/projectbrain"]
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"] = [
"Pull request / quality (pull_request)"
]
original_protection = copy.deepcopy(
fixture._branch_protections["Jens/projectbrain"][0]
)
try:
current = context()
preview = connector_instance.branch_protection_preview(
current, "projectbrain-repository"
)["data"]
self.assertEqual(preview["workflow"]["profile"], "projectbrain")
self.assertEqual(preview["workflow"]["action"], "none")
self.assertNotEqual(
preview["workflow"]["currentDigest"],
hashlib.sha256(MANAGED_VALIDATION_WORKFLOW.encode()).hexdigest(),
)
self.assertEqual(
preview["branchProtection"]["desired"]["status_check_contexts"],
[
"Managed validation / full (pull_request)",
"Pull request / quality (pull_request)",
],
)
self.assertEqual(preview["branchProtection"]["action"], "update")
applied = connector_instance.branch_protection_ensure(
current, "projectbrain-repository", preview["planHash"]
)["data"]
self.assertTrue(applied["changed"])
self.assertTrue(applied["verified"])
self.assertTrue(applied["compliant"])
self.assertEqual(fixture._managed_workflows["Jens/projectbrain"], original_workflow)
self.assertEqual(
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"],
[
"Managed validation / full (pull_request)",
"Pull request / quality (pull_request)",
],
)
self.assertEqual(
fixture._branch_protections["Jens/projectbrain"][0]["required_approvals"],
original_protection["required_approvals"],
)
workflow_writes = [
(method, path)
for method, path in zip(
fixture.recorded_methods, fixture.recorded_paths, strict=True
)
if path.endswith("/.gitea/workflows/managed-validation.yml")
and method in {"POST", "PUT", "DELETE"}
]
self.assertEqual(workflow_writes, [])
finally:
connector_instance.client.close()
def test_projectbrain_all_scope_fails_if_gitea_drops_an_extra_required_context(self) -> None:
connector_instance, fixture = projectbrain_connector()
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"] = [
"Pull request / quality (pull_request)",
"Additional required check",
]
original_read = connector_instance.client.branch_protections
read_count = 0
def drop_extra_context(repository: RegisteredRepository) -> list[dict[str, object]]:
nonlocal read_count
read_count += 1
if read_count == 3:
fixture._branch_protections["Jens/projectbrain"][0][
"status_check_contexts"
].remove("Additional required check")
return original_read(repository)
try:
current = context()
with patch.object(
connector_instance.client,
"branch_protections",
side_effect=drop_extra_context,
):
preview = connector_instance.branch_protection_preview(
current, "projectbrain-repository"
)["data"]
self.assertIn(
"Additional required check",
preview["branchProtection"]["desired"]["status_check_contexts"],
)
with self.assertRaisesRegex(GiteaError, "did not retain"):
connector_instance.branch_protection_ensure(
current, "projectbrain-repository", preview["planHash"]
)
self.assertNotIn(
"Additional required check",
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"],
)
finally:
connector_instance.client.close()
def test_projectbrain_all_scope_rejects_an_accepted_workflow_digest_change(self) -> None:
connector_instance, fixture = projectbrain_connector()
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"] = [
"Pull request / quality (pull_request)"
]
modified_workflow = PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" pull_request:\n", " pull_request:\n branches: [main]\n"
)
original_update = connector_instance.client.update_branch_protection
def update_protection_then_change_workflow(
repository: RegisteredRepository,
branch: str,
payload: dict[str, object],
) -> object:
response = original_update(repository, branch, payload)
fixture._managed_workflows["Jens/projectbrain"] = modified_workflow
return response
try:
current = context()
preview = connector_instance.branch_protection_preview(
current, "projectbrain-repository"
)["data"]
self.assertEqual(preview["workflow"]["profile"], "projectbrain")
with patch.object(
connector_instance.client,
"update_branch_protection",
side_effect=update_protection_then_change_workflow,
):
with self.assertRaisesRegex(GiteaError, "did not retain"):
connector_instance.branch_protection_ensure(
current, "projectbrain-repository", preview["planHash"]
)
self.assertEqual(
_managed_validation_profile("Jens/projectbrain", modified_workflow),
"projectbrain",
)
self.assertEqual(fixture._managed_workflows["Jens/projectbrain"], modified_workflow)
finally:
connector_instance.client.close()
def test_projectbrain_custom_profile_blocks_unsafe_existing_protection(self) -> None:
connector_instance, fixture = projectbrain_connector()
protection = fixture._branch_protections["Jens/projectbrain"][0]
protection["enable_push"] = True
protection["block_admin_merge_override"] = False
original_protection = copy.deepcopy(protection)
try:
current = context()
preview = connector_instance.branch_protection_preview(
current, "projectbrain-repository"
)["data"]
self.assertEqual(preview["workflow"]["profile"], "projectbrain")
self.assertIn(
"managed-validation-projectbrain-protection-safety-locks-invalid",
preview["blockers"],
)
with self.assertRaisesRegex(GiteaError, "unresolved blockers"):
connector_instance.branch_protection_ensure(
current, "projectbrain-repository", preview["planHash"]
)
self.assertEqual(
fixture._branch_protections["Jens/projectbrain"][0], original_protection
)
finally:
connector_instance.client.close()
def test_projectbrain_all_scope_creates_safe_protection_without_rewriting_profile(self) -> None:
connector_instance, fixture = projectbrain_connector()
original_workflow = fixture._managed_workflows["Jens/projectbrain"]
fixture._branch_protections["Jens/projectbrain"] = []
try:
current = context()
preview = connector_instance.branch_protection_preview(
current, "projectbrain-repository"
)["data"]
self.assertEqual(preview["workflow"]["profile"], "projectbrain")
self.assertEqual(preview["branchProtection"]["action"], "create")
applied = connector_instance.branch_protection_ensure(
current, "projectbrain-repository", preview["planHash"]
)["data"]
self.assertTrue(applied["compliant"])
self.assertTrue(applied["verified"])
self.assertEqual(fixture._managed_workflows["Jens/projectbrain"], original_workflow)
created_protection = fixture._branch_protections["Jens/projectbrain"][0]
self.assertEqual(
created_protection["status_check_contexts"],
[
"Managed validation / full (pull_request)",
"Pull request / quality (pull_request)",
],
)
self.assertTrue(created_protection["block_admin_merge_override"])
finally:
connector_instance.client.close()
def test_projectbrain_protection_only_is_hash_bound_and_preserves_workflow(
self,
) -> None:
connector_instance, fixture = projectbrain_connector()
invalid_workflow = PROJECTBRAIN_VALIDATION_WORKFLOW.replace(" pull_request:\n", "")
fixture._managed_workflows["Jens/projectbrain"] = invalid_workflow
protection = fixture._branch_protections["Jens/projectbrain"][0]
protection["status_check_contexts"] = [
"Pull request / quality (pull_request)",
"Additional required check",
]
protection["required_approvals"] = 2
protection["require_signed_commits"] = True
original_protection = copy.deepcopy(protection)
try:
current = context()
preview = connector_instance.branch_protection_preview(
current, "projectbrain-repository"
)["data"]
self.assertEqual(preview["workflow"]["profile"], "projectbrain-required")
self.assertEqual(preview["workflow"]["action"], "none")
self.assertIn("managed-validation-projectbrain-profile-invalid", preview["blockers"])
self.assertFalse(preview["compliant"])
self.assertTrue(preview["protectionOnlyAvailable"])
self.assertNotEqual(preview["planHash"], preview["protectionOnlyPlanHash"])
with self.assertRaisesRegex(GiteaError, "unresolved blockers"):
connector_instance.branch_protection_ensure(
current, "projectbrain-repository", preview["planHash"]
)
with self.assertRaisesRegex(GiteaError, "preview the repository again"):
connector_instance.branch_protection_ensure(
current,
"projectbrain-repository",
preview["planHash"],
apply_scope="protection_only",
)
self.assertEqual(fixture._managed_workflows["Jens/projectbrain"], invalid_workflow)
self.assertEqual(
fixture._branch_protections["Jens/projectbrain"][0], original_protection
)
portfolio_result = connector_instance.portfolio_governance_apply(
current,
[
{
"repositoryRef": "projectbrain-repository",
"planHash": preview["protectionOnlyPlanHash"],
"applyScope": "protection_only",
}
],
)["data"]
self.assertEqual(portfolio_result["summary"], {"total": 1, "applied": 1, "failed": 0})
self.assertEqual(portfolio_result["items"][0]["applyScope"], "protection_only")
updated_protection = fixture._branch_protections["Jens/projectbrain"][0]
self.assertEqual(
updated_protection["status_check_contexts"],
[
"Additional required check",
"Managed validation / full (pull_request)",
"Pull request / quality (pull_request)",
],
)
self.assertEqual(updated_protection["required_approvals"], 2)
self.assertTrue(updated_protection["require_signed_commits"])
self.assertEqual(fixture._managed_workflows["Jens/projectbrain"], invalid_workflow)
self.assertEqual(
fixture.recorded_bodies[-1],
{
"status_check_contexts": [
"Additional required check",
"Managed validation / full (pull_request)",
"Pull request / quality (pull_request)",
]
},
)
finally:
connector_instance.client.close()
def test_projectbrain_protection_only_requires_an_existing_enabled_rule(self) -> None:
connector_instance, fixture = projectbrain_connector()
fixture._managed_workflows["Jens/projectbrain"] = PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" pull_request:\n", ""
)
fixture._branch_protections["Jens/projectbrain"] = []
try:
preview = connector_instance.branch_protection_preview(
context(), "projectbrain-repository"
)["data"]
self.assertFalse(preview["protectionOnlyAvailable"])
self.assertIsNone(preview["protectionOnlyPlanHash"])
self.assertEqual(preview["branchProtection"]["action"], "create")
finally:
connector_instance.client.close()
def test_projectbrain_protection_only_fails_if_gitea_drops_an_extra_context(self) -> None:
connector_instance, fixture = projectbrain_connector()
fixture._managed_workflows["Jens/projectbrain"] = PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" pull_request:\n", ""
)
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"] = [
"Pull request / quality (pull_request)",
"Additional required check",
]
original_read = connector_instance.client.branch_protections
read_count = 0
def drop_extra_context(repository: RegisteredRepository) -> list[dict[str, object]]:
nonlocal read_count
read_count += 1
if read_count == 3:
fixture._branch_protections["Jens/projectbrain"][0][
"status_check_contexts"
].remove("Additional required check")
return original_read(repository)
try:
current = context()
with patch.object(
connector_instance.client,
"branch_protections",
side_effect=drop_extra_context,
):
preview = connector_instance.branch_protection_preview(
current, "projectbrain-repository"
)["data"]
self.assertTrue(preview["protectionOnlyAvailable"])
with self.assertRaisesRegex(GiteaError, "did not retain"):
connector_instance.branch_protection_ensure(
current,
"projectbrain-repository",
preview["protectionOnlyPlanHash"],
apply_scope="protection_only",
)
self.assertNotIn(
"Additional required check",
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"],
)
finally:
connector_instance.client.close()
def test_projectbrain_protection_only_rejects_other_protection_changes(self) -> None:
connector_instance, fixture = projectbrain_connector()
fixture._managed_workflows["Jens/projectbrain"] = PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
" pull_request:\n", ""
)
original_read = connector_instance.client.branch_protections
read_count = 0
def changed_protection_response(
repository: RegisteredRepository,
) -> list[dict[str, object]]:
nonlocal read_count
protections = original_read(repository)
read_count += 1
if read_count == 3:
protections[0]["required_approvals"] = 0
protections[0]["block_admin_merge_override"] = False
return protections
try:
current = context()
with patch.object(
connector_instance.client,
"branch_protections",
side_effect=changed_protection_response,
):
preview = connector_instance.branch_protection_preview(
current, "projectbrain-repository"
)["data"]
with self.assertRaisesRegex(GiteaError, "did not retain"):
connector_instance.branch_protection_ensure(
current,
"projectbrain-repository",
preview["protectionOnlyPlanHash"],
apply_scope="protection_only",
)
self.assertEqual(
fixture._managed_workflows["Jens/projectbrain"],
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(" pull_request:\n", ""),
)
finally:
connector_instance.client.close()
def test_governance_update_never_relaxes_protected_main_push(
self,
) -> None: