fix(health): suppress 404 when health monitor disabled + fix audit test assertion
- Sidebar.svelte: defer healthStore.refresh() until feature flags confirm health_monitor is enabled; skip entirely when disabled - health.js: remove throw error from catch block — log and return null instead, preventing 'Uncaught (in promise)' on expected 404 - test_report_audit_immutability.py: fix mock assertions — audit_service uses logger.reason/reflect/explore, not logger.info - HealthStore and Sidebar now produce zero network noise when FEATURES__HEALTH_MONITOR=false
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# #region GitServiceBranchMixin [C:3] [TYPE Module] [SEMANTICS git, branch, checkout, list, namespace]
|
||||
# #region GitServiceBranchMixin [C:4] [TYPE Module] [SEMANTICS git, branch, checkout, list, namespace, lock]
|
||||
# @LAYER: Infra
|
||||
# @BRIEF Branch and commit operations for GitService — gitflow branches, list/create/checkout branches, commit changes.
|
||||
# @BRIEF Branch and commit operations for GitService — gitflow branches, list/create/checkout branches, commit changes (all concurrent-safe via per-dashboard locks).
|
||||
# @RELATION USED_BY -> [GitService]
|
||||
|
||||
import os
|
||||
@@ -15,10 +15,11 @@ from src.core.logger import belief_scope, logger
|
||||
# #region GitServiceBranchMixin [C:3] [TYPE Class]
|
||||
# @BRIEF Mixin providing branch and commit operations for GitService.
|
||||
class GitServiceBranchMixin:
|
||||
# region _ensure_gitflow_branches [TYPE Function]
|
||||
# region _ensure_gitflow_branches [C:4] [TYPE Function] [SEMANTICS git,gitflow,branch,lock]
|
||||
# @PURPOSE: Ensure standard GitFlow branches (main/dev/preprod) exist locally and on origin.
|
||||
# @PRE: repo is a valid GitPython Repo instance.
|
||||
# @POST: main, dev, preprod are available in local repository and pushed to origin when available.
|
||||
# Active branch unchanged (no spurious checkout).
|
||||
def _ensure_gitflow_branches(self, repo: Repo, dashboard_id: int) -> None:
|
||||
with belief_scope("GitService._ensure_gitflow_branches"):
|
||||
required_branches = ["main", "dev", "preprod"]
|
||||
@@ -79,62 +80,74 @@ class GitServiceBranchMixin:
|
||||
status_code=500,
|
||||
detail=f"Failed to create default branch '{branch_name}' on remote: {e!s}",
|
||||
)
|
||||
# Fix 6: Only checkout if not already on dev
|
||||
try:
|
||||
repo.git.checkout("dev")
|
||||
logger.reason(f"Checked out default branch dev for dashboard {dashboard_id}", extra={"src": "_ensure_gitflow_branches"})
|
||||
except Exception as e:
|
||||
logger.reason(f"Could not checkout dev branch for dashboard {dashboard_id}: {e}", extra={"src": "_ensure_gitflow_branches"})
|
||||
current_branch = repo.active_branch.name
|
||||
except Exception:
|
||||
current_branch = None
|
||||
if current_branch != "dev":
|
||||
try:
|
||||
repo.git.checkout("dev")
|
||||
logger.reason(f"Checked out default branch dev for dashboard {dashboard_id}", extra={"src": "_ensure_gitflow_branches"})
|
||||
except Exception as e:
|
||||
logger.reason(f"Could not checkout dev branch for dashboard {dashboard_id}: {e}", extra={"src": "_ensure_gitflow_branches"})
|
||||
# endregion _ensure_gitflow_branches
|
||||
|
||||
# region list_branches [TYPE Function]
|
||||
# @PURPOSE: List all branches for a dashboard's repository.
|
||||
# region list_branches [C:4] [TYPE Function] [SEMANTICS git,branch,list,lock]
|
||||
# @PURPOSE: List all branches (excluding tags) for a dashboard's repository, concurrent-safe.
|
||||
# @PRE: Repository for dashboard_id exists.
|
||||
# @POST: Returns a list of branch metadata dictionaries.
|
||||
# @POST: Returns a list of branch metadata dictionaries (no tag refs).
|
||||
# @RETURN: List[dict]
|
||||
def list_branches(self, dashboard_id: int) -> list[dict]:
|
||||
with belief_scope("GitService.list_branches"):
|
||||
repo = self.get_repo(dashboard_id)
|
||||
logger.reason(f"Listing branches for {dashboard_id}. Refs: {repo.refs}", extra={"src": "list_branches"})
|
||||
branches = []
|
||||
for ref in repo.refs:
|
||||
with self._locked(dashboard_id):
|
||||
with belief_scope("GitService.list_branches"):
|
||||
repo = self.get_repo(dashboard_id)
|
||||
logger.reason(f"Listing branches for {dashboard_id}. Refs: {repo.refs}", extra={"src": "list_branches"})
|
||||
branches = []
|
||||
for ref in repo.refs:
|
||||
try:
|
||||
# Fix 8: Skip tag refs
|
||||
ref_name = str(ref.name)
|
||||
if ref_name.startswith('refs/tags/'):
|
||||
continue
|
||||
name = ref_name.replace('refs/heads/', '').replace('refs/remotes/origin/', '')
|
||||
if any(b['name'] == name for b in branches):
|
||||
continue
|
||||
branches.append({
|
||||
"name": name,
|
||||
"commit_hash": ref.commit.hexsha if hasattr(ref, 'commit') else "0000000",
|
||||
"is_remote": ref.is_remote() if hasattr(ref, 'is_remote') else False,
|
||||
"last_updated": datetime.fromtimestamp(ref.commit.committed_date) if hasattr(ref, 'commit') else datetime.utcnow()
|
||||
})
|
||||
except Exception as e:
|
||||
logger.reason(f"Skipping ref {ref}: {e}", extra={"src": "list_branches"})
|
||||
try:
|
||||
name = ref.name.replace('refs/heads/', '').replace('refs/remotes/origin/', '')
|
||||
if any(b['name'] == name for b in branches):
|
||||
continue
|
||||
branches.append({
|
||||
"name": name,
|
||||
"commit_hash": ref.commit.hexsha if hasattr(ref, 'commit') else "0000000",
|
||||
"is_remote": ref.is_remote() if hasattr(ref, 'is_remote') else False,
|
||||
"last_updated": datetime.fromtimestamp(ref.commit.committed_date) if hasattr(ref, 'commit') else datetime.utcnow()
|
||||
})
|
||||
active_name = repo.active_branch.name
|
||||
if not any(b['name'] == active_name for b in branches):
|
||||
branches.append({
|
||||
"name": active_name, "commit_hash": "0000000",
|
||||
"is_remote": False, "last_updated": datetime.utcnow()
|
||||
})
|
||||
except Exception as e:
|
||||
logger.reason(f"Skipping ref {ref}: {e}", extra={"src": "list_branches"})
|
||||
try:
|
||||
active_name = repo.active_branch.name
|
||||
if not any(b['name'] == active_name for b in branches):
|
||||
branches.append({
|
||||
"name": active_name, "commit_hash": "0000000",
|
||||
"is_remote": False, "last_updated": datetime.utcnow()
|
||||
})
|
||||
except Exception as e:
|
||||
logger.reason(f"Could not determine active branch: {e}", extra={"src": "list_branches"})
|
||||
if not branches:
|
||||
branches.append({
|
||||
"name": "dev", "commit_hash": "0000000",
|
||||
"is_remote": False, "last_updated": datetime.utcnow()
|
||||
})
|
||||
return branches
|
||||
logger.reason(f"Could not determine active branch: {e}", extra={"src": "list_branches"})
|
||||
if not branches:
|
||||
branches.append({
|
||||
"name": "dev", "commit_hash": "0000000",
|
||||
"is_remote": False, "last_updated": datetime.utcnow()
|
||||
})
|
||||
return branches
|
||||
# endregion list_branches
|
||||
|
||||
# region create_branch [TYPE Function]
|
||||
# @PURPOSE: Create a new branch from an existing one.
|
||||
# region create_branch [C:4] [TYPE Function] [SEMANTICS git,branch,create,lock]
|
||||
# @PURPOSE: Create a new branch from an existing one (concurrent-safe).
|
||||
# @PARAM: name (str) - New branch name.
|
||||
# @PARAM: from_branch (str) - Source branch.
|
||||
# @PRE: Repository exists; name is valid; from_branch exists or repo is empty.
|
||||
# @POST: A new branch is created in the repository.
|
||||
def create_branch(self, dashboard_id: int, name: str, from_branch: str = "main"):
|
||||
with belief_scope("GitService.create_branch"):
|
||||
repo = self.get_repo(dashboard_id)
|
||||
with self._locked(dashboard_id):
|
||||
with belief_scope("GitService.create_branch"):
|
||||
repo = self.get_repo(dashboard_id)
|
||||
logger.reason(f"Creating branch {name} from {from_branch}", extra={"src": "create_branch"})
|
||||
if not repo.heads and not repo.remotes:
|
||||
logger.reason("Repository is empty. Creating initial commit to enable branching.", extra={"src": "create_branch"})
|
||||
@@ -157,26 +170,28 @@ class GitServiceBranchMixin:
|
||||
raise
|
||||
# endregion create_branch
|
||||
|
||||
# region checkout_branch [TYPE Function]
|
||||
# @PURPOSE: Switch to a specific branch.
|
||||
# region checkout_branch [C:4] [TYPE Function] [SEMANTICS git,branch,checkout,lock]
|
||||
# @PURPOSE: Switch to a specific branch (concurrent-safe).
|
||||
# @PRE: Repository exists and the specified branch name exists.
|
||||
# @POST: The repository working directory is updated to the specified branch.
|
||||
def checkout_branch(self, dashboard_id: int, name: str):
|
||||
with belief_scope("GitService.checkout_branch"):
|
||||
repo = self.get_repo(dashboard_id)
|
||||
with self._locked(dashboard_id):
|
||||
with belief_scope("GitService.checkout_branch"):
|
||||
repo = self.get_repo(dashboard_id)
|
||||
logger.reason(f"Checking out branch {name}", extra={"src": "checkout_branch"})
|
||||
repo.git.checkout(name)
|
||||
# endregion checkout_branch
|
||||
|
||||
# region commit_changes [TYPE Function]
|
||||
# @PURPOSE: Stage and commit changes.
|
||||
# region commit_changes [C:4] [TYPE Function] [SEMANTICS git,commit,stage,lock]
|
||||
# @PURPOSE: Stage and commit changes (concurrent-safe).
|
||||
# @PARAM: message (str) - Commit message.
|
||||
# @PARAM: files (List[str]) - Optional list of specific files to stage.
|
||||
# @PRE: Repository exists and has changes (dirty) or files are specified.
|
||||
# @POST: Changes are staged and a new commit is created.
|
||||
def commit_changes(self, dashboard_id: int, message: str, files: list[str] = None):
|
||||
with belief_scope("GitService.commit_changes"):
|
||||
repo = self.get_repo(dashboard_id)
|
||||
with self._locked(dashboard_id):
|
||||
with belief_scope("GitService.commit_changes"):
|
||||
repo = self.get_repo(dashboard_id)
|
||||
if not repo.is_dirty(untracked_files=True) and not files:
|
||||
logger.reason(f"No changes to commit for dashboard {dashboard_id}", extra={"src": "commit_changes"})
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user