diff --git a/.tdd/spec-campus-project-work-summary-v1.md b/.tdd/spec-campus-project-work-summary-v1.md
new file mode 100644
index 0000000..1a66c28
--- /dev/null
+++ b/.tdd/spec-campus-project-work-summary-v1.md
@@ -0,0 +1,54 @@
+# Campus project work summary + GitHub Issue v1
+
+Status: LOCKED
+Issue: https://github.com/pirajoke/agent-dashboard/issues/34
+
+## Goal
+
+The read-only Pixel Verse Campus project inspector shows a concise description
+of the current verified work and a direct GitHub Issue link for owner-view live
+tasks, without publishing private task content.
+
+## Acceptance criteria
+
+- AC-01: A live project inspector can render `Над чем работаем` from a bounded,
+ privacy-safe `work_summary` value.
+- AC-02: A live project inspector can render one keyboard-operable external
+ GitHub Issue link whose visible label is `Issue #N`.
+- AC-03: Owner-view canonical Bridge tasks derive `work_summary` only from the
+ typed metadata `objective`, never from raw `description`, `result`, messages,
+ issue bodies, or logs.
+- AC-04: Owner-view issue data is accepted only when metadata contains an
+ integer `github_issue_number`, a matching `github_issue_url`, and an exact
+ `github_repo` identity. The URL must be HTTPS `github.com/{owner}/{repo}/issues/N`
+ with no credentials, port, query, or fragment.
+- AC-05: Verified MAIN MANAGER pixel events may carry the same owner-only
+ fields through the projection after identical validation.
+- AC-06: An anonymous public-host `/api/manager/departments` response never
+ contains `work_summary`, `issue_url`, or `issue_number`; a view authenticated
+ with the existing dashboard owner token may contain those owner-only fields,
+ including when accessed through the public host.
+
+## Edge cases
+
+- EC-01: Missing summary or issue data hides only the corresponding row.
+- EC-02: Summary text is whitespace-normalized and bounded without breaking
+ the existing responsive inspector.
+- EC-03: The link opens in a new tab with `rel="noreferrer"` and remains
+ read-only and keyboard accessible.
+
+## Errors and privacy
+
+- ERR-01: A mismatched issue number/repository, non-GitHub URL, credentials,
+ port, query, fragment, malformed type, or unsafe summary fails closed.
+- ERR-02: Raw task `description`, result, prompt/body, local paths, tokens,
+ credentials, logs, and private metadata never enter the projection or DOM.
+- ERR-03: Existing project details, focus return, Escape close, empty state,
+ three-lane cap, and public privacy behavior remain unchanged.
+
+## Constraints
+
+- No new dependency.
+- Source changes stay in `builder/`.
+- Read-only UI only; no dispatch, edit, merge, deploy, or publication action.
+- Draft PR only. Merge and production deployment are separate stages.
diff --git a/builder/dashboard-assets/script.js b/builder/dashboard-assets/script.js
index 511e0a8..8002e6a 100644
--- a/builder/dashboard-assets/script.js
+++ b/builder/dashboard-assets/script.js
@@ -111,6 +111,49 @@
unavailable: 'Команда на местах · live-данные временно недоступны',
active: 'Показаны свежие подтверждённые события',
};
+
+ function campusOwnerHeaders() {
+ const headers = {};
+ try {
+ const token = window.localStorage
+ .getItem('command-center.jarvis-run-token')
+ ?.trim();
+ if (token) headers['X-Dashboard-Run-Token'] = token;
+ } catch (_error) {
+ // Storage can be unavailable; the endpoint then returns its public projection.
+ }
+ return headers;
+ }
+
+ function verifiedCampusIssue(event) {
+ const issueNumber = event?.issue_number;
+ const issueUrl = event?.issue_url;
+ if (!Number.isInteger(issueNumber) || issueNumber <= 0 || typeof issueUrl !== 'string') {
+ return null;
+ }
+ try {
+ const parsed = new URL(issueUrl);
+ const match = parsed.pathname.match(
+ /^\/[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*\/issues\/([1-9]\d*)$/,
+ );
+ if (
+ parsed.protocol !== 'https:'
+ || parsed.hostname !== 'github.com'
+ || parsed.username
+ || parsed.password
+ || parsed.port
+ || parsed.search
+ || parsed.hash
+ || !match
+ || Number(match[1]) !== issueNumber
+ ) {
+ return null;
+ }
+ } catch (_error) {
+ return null;
+ }
+ return {number: issueNumber, url: issueUrl};
+ }
let lastTrigger = null;
let intervalId = null;
let refreshInFlight = false;
@@ -241,6 +284,10 @@
? event.next_step
: null;
const hasEvidence = Number.isInteger(event?.evidence_count) && event.evidence_count >= 0;
+ const workSummary = typeof event?.work_summary === 'string' && event.work_summary.trim()
+ ? event.work_summary
+ : null;
+ const issue = verifiedCampusIssue(event);
projectDetailFields.project.textContent = (
folder.dataset.campusProjectLabel || folder.dataset.campusProject || '—'
);
@@ -251,6 +298,13 @@
projectDetailFields.status.textContent = event
? (statusLabels[event.status] || '—')
: 'нет активных задач';
+ projectDetailFields.work_summary.textContent = workSummary || '—';
+ projectDetailFields.issue_url.removeAttribute('href');
+ projectDetailFields.issue_url.textContent = '—';
+ if (issue) {
+ projectDetailFields.issue_url.setAttribute('href', issue.url);
+ projectDetailFields.issue_url.textContent = `Issue #${issue.number}`;
+ }
projectDetailFields.next_step.textContent = nextStep || '—';
projectDetailFields.evidence_count.textContent = hasEvidence
? String(event.evidence_count)
@@ -258,7 +312,13 @@
projectLiveOnlyEls.forEach((row) => {
const field = row.querySelector('[data-campus-project-detail-field]')
?.dataset.campusProjectDetailField;
- row.hidden = field === 'next_step' ? !nextStep : !hasEvidence;
+ const visible = {
+ work_summary: Boolean(workSummary),
+ issue_url: Boolean(issue),
+ next_step: Boolean(nextStep),
+ evidence_count: hasEvidence,
+ };
+ row.hidden = !visible[field];
});
projectDetailEl.hidden = false;
projectDetailCloseEl?.focus();
@@ -497,6 +557,7 @@
const response = await fetch('/api/manager/departments', {
cache: 'no-store',
signal: controller.signal,
+ headers: campusOwnerHeaders(),
});
if (!response.ok) throw new Error('unavailable');
const payload = await response.json();
diff --git a/builder/dashboard-server-m4.py b/builder/dashboard-server-m4.py
index cd59c76..d29548a 100644
--- a/builder/dashboard-server-m4.py
+++ b/builder/dashboard-server-m4.py
@@ -699,10 +699,15 @@ def _github_bridge_reconciliation(issues: list[dict], bridge_tasks: list[dict])
}
-def _dashboard_run_token() -> str:
+def _dashboard_run_token(*, create_if_missing: bool = True) -> str:
token = os.environ.get("DASHBOARD_RUN_TOKEN", "").strip()
if token:
return token
+ if not create_if_missing:
+ try:
+ return JARVIS_DASHBOARD_RUN_TOKEN_FILE.read_text().strip()
+ except FileNotFoundError:
+ return ""
JARVIS_DASHBOARD_RUN_TOKEN_FILE.parent.mkdir(parents=True, exist_ok=True)
if not JARVIS_DASHBOARD_RUN_TOKEN_FILE.exists() or not JARVIS_DASHBOARD_RUN_TOKEN_FILE.read_text().strip():
JARVIS_DASHBOARD_RUN_TOKEN_FILE.write_text(secrets.token_urlsafe(24) + "\n")
@@ -1250,7 +1255,7 @@ def _campus_bridge_event(task: dict) -> dict | None:
if not isinstance(task_id, str):
return None
zone = DEPARTMENT_ZONES[project["department_id"]]
- return {
+ event = {
"event_id": task_id,
"task_id": task_id,
"department_id": project["department_id"],
@@ -1265,16 +1270,27 @@ def _campus_bridge_event(task: dict) -> dict | None:
"ephemeral": True,
"zone_id": zone["zone_id"],
}
+ metadata = _manager_metadata(task)
+ event["work_summary"] = metadata.get("objective")
+ event["github_repo"] = metadata.get("github_repo")
+ event["github_issue_number"] = metadata.get("github_issue_number")
+ event["github_issue_url"] = metadata.get("github_issue_url")
+ return event
-def _department_campus_payload(data: object, *, now: datetime | None = None) -> dict:
+def _department_campus_payload(
+ data: object,
+ *,
+ now: datetime | None = None,
+ owner_view: bool = False,
+) -> dict:
"""Project a verified manager snapshot or safe canonical Bridge tasks."""
current = now or datetime.now(timezone.utc)
if current.tzinfo is None:
current = current.replace(tzinfo=timezone.utc)
current = current.astimezone(timezone.utc)
if not isinstance(data, dict) or not isinstance(data.get("tasks"), list):
- return department_campus_projection(None, now=current)
+ return department_campus_projection(None, now=current, owner_view=owner_view)
candidates: list[tuple[int, datetime, list]] = []
malformed_verified_snapshot = False
@@ -1307,7 +1323,7 @@ def _department_campus_payload(data: object, *, now: datetime | None = None) ->
if not candidates:
if malformed_verified_snapshot:
- return department_campus_projection(None, now=current)
+ return department_campus_projection(None, now=current, owner_view=owner_view)
events = [
event
for task in data["tasks"]
@@ -1315,13 +1331,23 @@ def _department_campus_payload(data: object, *, now: datetime | None = None) ->
for event in [_campus_bridge_event(task)]
if event is not None
]
- return department_campus_projection(events, now=current, max_tasks=3)
+ return department_campus_projection(
+ events,
+ now=current,
+ max_tasks=3,
+ owner_view=owner_view,
+ )
# max() preserves the first source item when timestamps tie.
_, snapshot_time, events = max(candidates, key=lambda item: item[1])
if (current - snapshot_time).total_seconds() > 30 * 60:
return _department_campus_state("stale", now=current)
- return department_campus_projection(events, now=current, max_tasks=3)
+ return department_campus_projection(
+ events,
+ now=current,
+ max_tasks=3,
+ owner_view=owner_view,
+ )
def _runtime_asset_block(filename: str, start_marker: str, end_marker: str) -> str:
@@ -1392,9 +1418,19 @@ def _read_json_body(self, max_bytes: int = 4096) -> dict:
def _dashboard_run_authorized(self) -> bool:
if not self._is_public_request():
return True
- expected = _dashboard_run_token()
+ write_method = getattr(self, "command", "").upper() in {
+ "POST",
+ "PUT",
+ "PATCH",
+ "DELETE",
+ }
+ expected = _dashboard_run_token(create_if_missing=write_method)
provided = self.headers.get("X-Dashboard-Run-Token", "").strip()
- return bool(provided) and secrets.compare_digest(provided, expected)
+ return (
+ bool(expected)
+ and bool(provided)
+ and secrets.compare_digest(provided, expected)
+ )
def _require_dashboard_run_auth(self) -> bool:
if self._dashboard_run_authorized():
@@ -2244,7 +2280,11 @@ def do_GET(self):
if parsed.path == '/api/manager/departments':
try:
data = _bridge_request("GET", "/api/tasks?limit=24&include_messages=1")
- self._json_response(200, _department_campus_payload(data))
+ if self._dashboard_run_authorized():
+ payload = _department_campus_payload(data, owner_view=True)
+ else:
+ payload = _department_campus_payload(data)
+ self._json_response(200, payload)
except Exception:
self._json_response(
200,
diff --git a/builder/dashboard_builder/department_campus.py b/builder/dashboard_builder/department_campus.py
index 7a0d89c..027f632 100644
--- a/builder/dashboard_builder/department_campus.py
+++ b/builder/dashboard_builder/department_campus.py
@@ -268,6 +268,11 @@ def campus_project_for_event(event: object) -> MappingProxyType | None:
"ephemeral",
"zone_id",
)
+_OWNER_EVENT_FIELDS = (
+ "work_summary",
+ "issue_number",
+ "issue_url",
+)
_FRESH_SECONDS = 30 * 60
_SAFE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,95}$")
_UNSAFE_TEXT = re.compile(
@@ -412,6 +417,55 @@ def _safe_text(value: object, *, limit: int = 180) -> str:
return _bounded_grapheme_text(value, limit)
+def _safe_owner_summary(value: object, *, limit: int = 240) -> str | None:
+ """Return a bounded owner summary, rejecting rather than repairing unsafe input."""
+ if not isinstance(value, str):
+ return None
+ normalized = unicodedata.normalize("NFKC", value)
+ summary = " ".join(normalized.strip().split())
+ if (
+ not summary
+ or _strip_unsafe_formatting(summary) != summary
+ or _UNSAFE_TEXT.search(summary)
+ ):
+ return None
+ bounded = _bounded_grapheme_text(summary, limit)
+ return bounded or None
+
+
+def _validated_owner_fields(event: dict[str, Any]) -> dict[str, Any]:
+ """Validate optional owner-only fields independently and fail closed."""
+ owner_fields: dict[str, Any] = {}
+ summary = _safe_owner_summary(event.get("work_summary"))
+ if summary is not None:
+ owner_fields["work_summary"] = summary
+
+ repo = event.get("github_repo")
+ issue_number = event.get("github_issue_number")
+ issue_url = event.get("github_issue_url")
+ repo_parts = repo.split("/", 1) if isinstance(repo, str) else ()
+ github_owner = repo_parts[0] if len(repo_parts) == 2 else ""
+ github_name = repo_parts[1] if len(repo_parts) == 2 else ""
+ if (
+ not isinstance(repo, str)
+ or not re.fullmatch(
+ r"[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?",
+ github_owner,
+ )
+ or not re.fullmatch(r"[A-Za-z0-9._-]{1,100}", github_name)
+ or github_name in {".", ".."}
+ or type(issue_number) is not int
+ or issue_number <= 0
+ or not isinstance(issue_url, str)
+ or issue_url != f"https://github.com/{repo}/issues/{issue_number}"
+ ):
+ return owner_fields
+
+ owner_fields["issue_number"] = issue_number
+ owner_fields["issue_url"] = issue_url
+ return owner_fields
+
+
def _empty_projection(state: str, now: datetime) -> dict[str, Any]:
return {
"state": state,
@@ -427,6 +481,7 @@ def _validated_event(
event: object,
*,
now: datetime,
+ owner_view: bool = False,
) -> tuple[dict[str, Any] | None, str, datetime | None]:
if not isinstance(event, dict):
return None, "invalid", None
@@ -477,6 +532,8 @@ def _validated_event(
"ephemeral": True,
"zone_id": zone["zone_id"],
}
+ if owner_view is True:
+ public.update(_validated_owner_fields(event))
return public, "valid", updated
@@ -485,8 +542,9 @@ def department_campus_projection(
*,
now: datetime,
max_tasks: int = 3,
+ owner_view: bool = False,
) -> dict[str, Any]:
- """Return a strict, fresh public projection of verified pixel events."""
+ """Return a strict projection, optionally including validated owner fields."""
if not isinstance(events, list):
return _empty_projection("unavailable", now)
if not events:
@@ -495,7 +553,11 @@ def department_campus_projection(
validated: list[tuple[int, dict[str, Any], datetime]] = []
saw_stale = False
for index, raw_event in enumerate(events):
- event, validation_state, updated = _validated_event(raw_event, now=now)
+ event, validation_state, updated = _validated_event(
+ raw_event,
+ now=now,
+ owner_view=owner_view,
+ )
saw_stale = saw_stale or validation_state == "stale"
if event is not None and updated is not None:
validated.append((index, event, updated))
@@ -531,7 +593,11 @@ def department_campus_projection(
"visible_task_count": len(visible_tasks),
"omitted_task_count": max(0, len(all_tasks) - len(visible_tasks)),
"events": [
- {field: event[field] for field in _PUBLIC_EVENT_FIELDS}
+ {
+ field: event[field]
+ for field in _PUBLIC_EVENT_FIELDS + _OWNER_EVENT_FIELDS
+ if field in event
+ }
for event in visible_events
],
"privacy": "public_projection",
@@ -674,18 +740,28 @@ def build_department_campus_html() -> str:
("department_id", "Отдел", False),
("agent_id", "Ответственный агент", False),
("status", "Статус", False),
+ ("work_summary", "Над чем работаем", True),
+ ("issue_url", "GitHub Issue", True),
("next_step", "Следующий безопасный шаг", True),
("evidence_count", "Подтверждения", True),
)
- project_detail_rows = "".join(
- (
+ project_detail_rows = []
+ for field, label, live_only in project_details:
+ row_start = (
'
'
if live_only
else "
"
)
- + f'
{label}—'
- for field, label, live_only in project_details
- )
+ if field == "issue_url":
+ value = (
+ '
—'
+ )
+ else:
+ value = f'
—'
+ project_detail_rows.append(f"{row_start}
{label}{value}
")
+ project_detail_rows_html = "".join(project_detail_rows)
return f"""
@@ -728,7 +804,7 @@ def build_department_campus_html() -> str:
-
{project_detail_rows}
+
{project_detail_rows_html}
diff --git a/builder/tests/test_department_campus_projects.py b/builder/tests/test_department_campus_projects.py
index 6c92c8f..0daa38d 100644
--- a/builder/tests/test_department_campus_projects.py
+++ b/builder/tests/test_department_campus_projects.py
@@ -174,8 +174,20 @@ def test_ac_4_folders_share_one_keyboard_operable_detail_surface(self):
def test_ac_5_err_1_detail_and_projection_are_strict_safe_allowlists(self):
fields = re.findall(r'data-campus-project-detail-field="([^"]+)"', self.html)
- self.assertEqual(fields, ["project", "department_id", "agent_id", "status", "next_step", "evidence_count"])
- for field in ("next_step", "evidence_count"):
+ self.assertEqual(
+ fields,
+ [
+ "project",
+ "department_id",
+ "agent_id",
+ "status",
+ "work_summary",
+ "issue_url",
+ "next_step",
+ "evidence_count",
+ ],
+ )
+ for field in ("work_summary", "issue_url", "next_step", "evidence_count"):
self.assertRegex(
self.html,
rf'data-campus-project-live-only[^>]*[\s\S]{{0,180}}data-campus-project-detail-field="{field}"',
diff --git a/builder/tests/test_department_campus_work_summary.py b/builder/tests/test_department_campus_work_summary.py
new file mode 100644
index 0000000..cafe703
--- /dev/null
+++ b/builder/tests/test_department_campus_work_summary.py
@@ -0,0 +1,456 @@
+from __future__ import annotations
+
+from datetime import datetime, timezone
+import importlib
+import importlib.util
+import os
+from pathlib import Path
+import re
+import tempfile
+import unittest
+from unittest.mock import patch
+
+
+BUILDER_DIR = Path(__file__).resolve().parents[1]
+ASSETS_DIR = BUILDER_DIR / "dashboard-assets"
+SERVER_PATH = BUILDER_DIR / "dashboard-server-m4.py"
+PUBLIC_EVENT_FIELDS = {
+ "event_id", "task_id", "department_id", "department_label", "project",
+ "agent_id", "role", "status", "updated_at", "next_step",
+ "evidence_count", "ephemeral", "zone_id",
+}
+OWNER_ONLY_EVENT_FIELDS = {"work_summary", "issue_number", "issue_url"}
+
+
+class DepartmentCampusWorkSummaryTests(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls) -> None:
+ server_spec = importlib.util.spec_from_file_location(
+ "dashboard_server_campus_work_summary", SERVER_PATH
+ )
+ if server_spec is None or server_spec.loader is None:
+ raise AssertionError("RED: dashboard server module cannot be loaded")
+ cls.server = importlib.util.module_from_spec(server_spec)
+ server_spec.loader.exec_module(cls.server)
+ cls.campus = importlib.import_module("dashboard_builder.department_campus")
+ cls.html = cls.campus.build_department_campus_html()
+ cls.script = (ASSETS_DIR / "script.js").read_text(encoding="utf-8")
+ cls.css = (ASSETS_DIR / "style.css").read_text(encoding="utf-8")
+
+ def _bridge_task(self, *, metadata: dict | None = None, **overrides) -> dict:
+ now = datetime.now(timezone.utc).isoformat()
+ task = {
+ "id": "bridge-campus-34",
+ "status": "running",
+ "agent_role": "BUILDER",
+ "project": "JARVIS",
+ "created_at": now,
+ "claimed_at": now,
+ "completed_at": None,
+ "description": "FORBIDDEN_DESCRIPTION_CAMPUS_34",
+ "result": "FORBIDDEN_RESULT_CAMPUS_34",
+ "error": "FORBIDDEN_LOG_CAMPUS_34",
+ "messages": [{"body": "FORBIDDEN_MESSAGE_CAMPUS_34"}],
+ "metadata": {
+ "event": "dispatch",
+ "objective": " Добавить краткую сводку\nи ссылку на Issue ",
+ "github_repo": "pirajoke/jarvis",
+ "github_issue_number": 34,
+ "github_issue_url": "https://github.com/pirajoke/jarvis/issues/34",
+ "github_issue_body": "FORBIDDEN_ISSUE_BODY_CAMPUS_34",
+ "prompt": "FORBIDDEN_PROMPT_CAMPUS_34",
+ "body": "FORBIDDEN_BODY_CAMPUS_34",
+ "logs": "FORBIDDEN_LOG_METADATA_CAMPUS_34",
+ "local_path": "/Users/private/FORBIDDEN_PATH_CAMPUS_34",
+ "token": "FORBIDDEN_TOKEN_CAMPUS_34",
+ },
+ }
+ if metadata is not None:
+ task["metadata"] = metadata
+ task.update(overrides)
+ return task
+
+ def _pixel_event(self, **overrides) -> dict:
+ zone = self.campus.DEPARTMENT_ZONES["infrastructure"]
+ event = {
+ "event_id": "evt-campus-summary-34",
+ "task_id": "task-campus-summary-34",
+ "department_id": "infrastructure",
+ "department_label": zone["label"],
+ "project": "JARVIS",
+ "agent_id": "INFRASTRUCTURE",
+ "role": zone["roles"][0],
+ "status": "active",
+ "updated_at": datetime.now(timezone.utc).isoformat(),
+ "next_step": "Review safe evidence",
+ "evidence_count": 1,
+ "ephemeral": True,
+ "zone_id": zone["zone_id"],
+ "work_summary": "Проверить owner-проекцию",
+ "github_repo": "pirajoke/jarvis",
+ "github_issue_number": 34,
+ "github_issue_url": "https://github.com/pirajoke/jarvis/issues/34",
+ }
+ event.update(overrides)
+ return event
+
+ def _manager_snapshot(self, event: dict, **metadata_overrides) -> dict:
+ metadata = {
+ "event": "status",
+ "source_agent": "MAIN MANAGER",
+ "pixel_events": [event],
+ }
+ metadata.update(metadata_overrides)
+ return {
+ "id": "manager-snapshot-campus-34",
+ "status": "running",
+ "updated_at": datetime.now(timezone.utc).isoformat(),
+ "description": "FORBIDDEN_MANAGER_BODY_CAMPUS_34",
+ "result": "FORBIDDEN_MANAGER_RESULT_CAMPUS_34",
+ "messages": [{"body": "FORBIDDEN_MANAGER_MESSAGE_CAMPUS_34"}],
+ "metadata": metadata,
+ }
+
+ def _endpoint_payload(self, tasks: list[dict], *, owner: bool) -> dict:
+ response: dict = {}
+ handler = self.server.Handler.__new__(self.server.Handler)
+ handler.path = "/api/manager/departments"
+ handler.headers = {"Host": "command.meshly.fr"}
+ handler._dashboard_run_authorized = lambda: owner
+ handler._json_response = lambda status, payload: response.update(
+ status=status, payload=payload
+ )
+ with patch.object(
+ self.server, "_bridge_request", return_value={"tasks": tasks}
+ ):
+ handler.do_GET()
+ self.assertEqual(response.get("status"), 200)
+ return response["payload"]
+
+ def _owner_event(self, task: dict) -> dict:
+ payload = self._endpoint_payload([task], owner=True)
+ self.assertEqual(payload["state"], "active")
+ self.assertEqual(len(payload["events"]), 1)
+ return payload["events"][0]
+
+ def _public_departments_get(
+ self,
+ tasks: list[dict],
+ *,
+ provided_token: str | None,
+ ) -> dict:
+ response: dict = {}
+ handler = self.server.Handler.__new__(self.server.Handler)
+ handler.path = "/api/manager/departments"
+ handler.headers = {"Host": "command.meshly.fr"}
+ if provided_token is not None:
+ handler.headers["X-Dashboard-Run-Token"] = provided_token
+ handler._json_response = lambda status, payload: response.update(
+ status=status, payload=payload
+ )
+ with patch.object(
+ self.server, "_bridge_request", return_value={"tasks": tasks}
+ ):
+ handler.do_GET()
+ self.assertEqual(response.get("status"), 200)
+ return response["payload"]
+
+ def _campus_script(self) -> str:
+ start = self.script.index("// ── Department Campus ──")
+ end = self.script.index("// ── End Department Campus ──", start)
+ return self.script[start:end]
+
+ def test_ac01_ac02_ec01_ec03_inspector_has_optional_summary_and_issue_link(self):
+ self.assertEqual(self.html.count("Над чем работаем"), 1)
+ self.assertRegex(
+ self.html,
+ r'data-campus-project-live-only[^>]*hidden[\s\S]{0,240}'
+ r'data-campus-project-detail-field="work_summary"',
+ )
+ self.assertEqual(self.html.count("data-campus-project-issue-link"), 1)
+ issue_link = re.search(
+ r']*data-campus-project-issue-link)([^>]*)>', self.html
+ )
+ self.assertIsNotNone(issue_link, "the inspector needs one semantic Issue link")
+ attributes = issue_link.group(1)
+ self.assertIn('data-campus-project-detail-field="issue_url"', attributes)
+ self.assertIn('target="_blank"', attributes)
+ rel = re.search(r'rel="([^"]+)"', attributes)
+ self.assertIsNotNone(rel)
+ self.assertIn("noreferrer", rel.group(1).split())
+ self.assertNotRegex(attributes, r'\btabindex="-1"')
+ self.assertRegex(
+ self.html,
+ r'data-campus-project-live-only[^>]*hidden[\s\S]{0,300}'
+ r'data-campus-project-issue-link',
+ )
+ campus_script = self._campus_script()
+ for field in ("work_summary", "issue_number", "issue_url"):
+ self.assertIn(field, campus_script)
+ self.assertIn("command-center.jarvis-run-token", campus_script)
+ self.assertIn("X-Dashboard-Run-Token", campus_script)
+ self.assertRegex(campus_script, r"fetch\([\s\S]{0,500}\bheaders\s*:")
+ self.assertIn("Issue #", campus_script)
+ self.assertRegex(campus_script, r"removeAttribute\(\s*['\"]href['\"]\s*\)")
+ self.assertRegex(campus_script, r"\.hidden\s*=")
+ self.assertIn("textContent", campus_script)
+ self.assertNotRegex(campus_script, r"\.innerHTML\s*=")
+
+ def test_ac03_err02_owner_bridge_uses_only_typed_objective_and_exact_allowlist(self):
+ event = self._owner_event(self._bridge_task())
+ self.assertEqual(event["work_summary"], "Добавить краткую сводку и ссылку на Issue")
+ self.assertEqual(event["issue_number"], 34)
+ self.assertEqual(
+ event["issue_url"], "https://github.com/pirajoke/jarvis/issues/34"
+ )
+ self.assertEqual(set(event), PUBLIC_EVENT_FIELDS | OWNER_ONLY_EVENT_FIELDS)
+ rendered = repr(event)
+ for forbidden in (
+ "FORBIDDEN_DESCRIPTION_CAMPUS_34", "FORBIDDEN_RESULT_CAMPUS_34",
+ "FORBIDDEN_LOG_CAMPUS_34", "FORBIDDEN_MESSAGE_CAMPUS_34",
+ ):
+ self.assertNotIn(forbidden, rendered)
+
+ metadata_without_objective = dict(self._bridge_task()["metadata"])
+ metadata_without_objective.pop("objective")
+ fallback = self._owner_event(self._bridge_task(
+ metadata=metadata_without_objective,
+ description="SAFE LOOKING DESCRIPTION MUST NOT BECOME SUMMARY",
+ result="SAFE LOOKING RESULT MUST NOT BECOME SUMMARY",
+ ))
+ self.assertNotIn("work_summary", fallback)
+ self.assertNotIn("SAFE LOOKING", repr(fallback))
+
+ def test_ac04_err01_issue_metadata_must_be_exact_and_fails_closed_independently(self):
+ base = dict(self._bridge_task()["metadata"])
+ invalid_cases = (
+ {"github_issue_number": True},
+ {"github_issue_number": "34"},
+ {"github_issue_number": 0},
+ {"github_issue_number": 35},
+ {"github_repo": "pirajoke/agent-dashboard"},
+ {"github_repo": "PIRAJOKE/jarvis"},
+ {"github_repo": ["pirajoke", "jarvis"]},
+ {"github_issue_url": "http://github.com/pirajoke/jarvis/issues/34"},
+ {"github_issue_url": "https://evil.example/pirajoke/jarvis/issues/34"},
+ {"github_issue_url": "https://user@github.com/pirajoke/jarvis/issues/34"},
+ {"github_issue_url": "https://github.com:443/pirajoke/jarvis/issues/34"},
+ {"github_issue_url": "https://github.com/pirajoke/jarvis/issues/34?tab=1"},
+ {"github_issue_url": "https://github.com/pirajoke/jarvis/issues/34#issuecomment-1"},
+ {"github_issue_url": "https://github.com/pirajoke/jarvis/issues/35"},
+ {"github_issue_url": "https://github.com/PIRAJOKE/jarvis/issues/34"},
+ {"github_issue_url": "https://github.com/pirajoke/jarvis/pull/34"},
+ {"github_issue_url": 34},
+ )
+ for invalid in invalid_cases:
+ with self.subTest(invalid=invalid):
+ event = self._owner_event(
+ self._bridge_task(metadata={**base, **invalid})
+ )
+ self.assertEqual(
+ event.get("work_summary"),
+ "Добавить краткую сводку и ссылку на Issue",
+ "invalid issue data must hide only the Issue row",
+ )
+ self.assertNotIn("issue_number", event)
+ self.assertNotIn("issue_url", event)
+
+ for missing in ("github_repo", "github_issue_number", "github_issue_url"):
+ with self.subTest(missing=missing):
+ metadata = dict(base)
+ metadata.pop(missing)
+ event = self._owner_event(self._bridge_task(metadata=metadata))
+ self.assertNotIn("issue_number", event)
+ self.assertNotIn("issue_url", event)
+
+ def test_ac05_verified_manager_pixel_event_uses_identical_owner_field_validation(self):
+ valid = self._owner_event(self._manager_snapshot(self._pixel_event()))
+ self.assertEqual(valid["work_summary"], "Проверить owner-проекцию")
+ self.assertEqual(valid["issue_number"], 34)
+ self.assertEqual(
+ valid["issue_url"], "https://github.com/pirajoke/jarvis/issues/34"
+ )
+
+ mismatched = self._pixel_event(
+ github_issue_url="https://github.com/pirajoke/jarvis/issues/35"
+ )
+ rejected_issue = self._owner_event(self._manager_snapshot(mismatched))
+ self.assertEqual(rejected_issue["work_summary"], "Проверить owner-проекцию")
+ self.assertNotIn("issue_number", rejected_issue)
+ self.assertNotIn("issue_url", rejected_issue)
+
+ unverified = self._endpoint_payload(
+ [self._manager_snapshot(self._pixel_event(), source_agent="OTHER AGENT")],
+ owner=True,
+ )
+ self.assertEqual(unverified["events"], [])
+ self.assertNotIn("Проверить owner-проекцию", repr(unverified))
+
+ def test_ac06_err02_public_endpoint_strips_owner_fields_and_all_raw_content(self):
+ public = self._endpoint_payload([self._bridge_task()], owner=False)
+ self.assertEqual(public["state"], "active")
+ self.assertEqual(len(public["events"]), 1)
+ self.assertEqual(set(public["events"][0]), PUBLIC_EVENT_FIELDS)
+ rendered = repr(public)
+ for forbidden in (
+ "work_summary", "issue_number", "issue_url", "github_repo",
+ "github_issue_number", "github_issue_url", "Добавить краткую",
+ "FORBIDDEN_",
+ ):
+ self.assertNotIn(forbidden, rendered)
+
+ def test_err04_public_departments_get_never_creates_a_missing_dashboard_token(self):
+ for provided_token in (None, "wrong-token"):
+ with self.subTest(provided_token=provided_token):
+ with tempfile.TemporaryDirectory() as tmp:
+ token_path = Path(tmp) / "missing-parent" / "dashboard-token"
+ with (
+ patch.object(
+ self.server,
+ "JARVIS_DASHBOARD_RUN_TOKEN_FILE",
+ token_path,
+ ),
+ patch.dict(os.environ, {"DASHBOARD_RUN_TOKEN": ""}),
+ ):
+ payload = self._public_departments_get(
+ [self._bridge_task()],
+ provided_token=provided_token,
+ )
+
+ self.assertEqual(payload["privacy"], "public_projection")
+ self.assertNotIn("work_summary", repr(payload))
+ self.assertFalse(
+ token_path.parent.exists(),
+ "a public read must not create token storage",
+ )
+
+ def test_ac07_existing_or_env_dashboard_token_authorizes_without_mutation(self):
+ for token_source in ("file", "env"):
+ with self.subTest(token_source=token_source):
+ with tempfile.TemporaryDirectory() as tmp:
+ token_path = Path(tmp) / "token-store" / "dashboard-token"
+ environment = {"DASHBOARD_RUN_TOKEN": ""}
+ original_bytes = None
+ original_mtime_ns = None
+ if token_source == "file":
+ token_path.parent.mkdir()
+ token_path.write_text("owner-token\n", encoding="utf-8")
+ original_bytes = token_path.read_bytes()
+ original_mtime_ns = token_path.stat().st_mtime_ns
+ else:
+ environment["DASHBOARD_RUN_TOKEN"] = "owner-token"
+
+ with (
+ patch.object(
+ self.server,
+ "JARVIS_DASHBOARD_RUN_TOKEN_FILE",
+ token_path,
+ ),
+ patch.dict(os.environ, environment),
+ ):
+ payload = self._public_departments_get(
+ [self._bridge_task()],
+ provided_token="owner-token",
+ )
+
+ self.assertEqual(payload["events"][0]["issue_number"], 34)
+ self.assertIn("work_summary", payload["events"][0])
+ if token_source == "file":
+ self.assertEqual(token_path.read_bytes(), original_bytes)
+ self.assertEqual(token_path.stat().st_mtime_ns, original_mtime_ns)
+ else:
+ self.assertFalse(token_path.exists())
+
+ def test_ec01_err01_missing_malformed_or_unsafe_summary_hides_only_summary(self):
+ base = dict(self._bridge_task()["metadata"])
+ unsafe_objectives = (
+ None,
+ True,
+ 34,
+ ["private"],
+ {"body": "private"},
+ " \n\t ",
+ "Open /Users/mark/.ssh/id_rsa",
+ "token=CAMPUS_GENERIC_TOKEN_1234567890",
+ "Authorization: Bearer CAMPUS_BEARER_1234567890",
+ "ghp_CAMPUS012345678901234567890123456",
+ "Safe\x00\x1b[31mCAMPUS_CONTROL",
+ )
+ for objective in unsafe_objectives:
+ with self.subTest(objective=repr(objective)[:40]):
+ event = self._owner_event(
+ self._bridge_task(metadata={**base, "objective": objective})
+ )
+ self.assertNotIn("work_summary", event)
+ self.assertEqual(event["issue_number"], 34)
+ self.assertEqual(
+ event["issue_url"],
+ "https://github.com/pirajoke/jarvis/issues/34",
+ "unsafe summary must hide only the summary row",
+ )
+ self.assertNotIn("CAMPUS_", repr(event))
+ self.assertNotIn("/Users/", repr(event))
+
+ def test_ec02_summary_is_whitespace_normalized_unicode_safe_and_bounded(self):
+ objective = " Улучшить\n\tсводку " + ("👩\u200d💻" * 1000)
+ metadata = {**self._bridge_task()["metadata"], "objective": objective}
+ summary = self._owner_event(self._bridge_task(metadata=metadata))["work_summary"]
+ self.assertTrue(summary.startswith("Улучшить сводку "))
+ self.assertNotRegex(summary, r"\s{2,}|[\r\n\t]")
+ self.assertLess(len(summary), len(" ".join(objective.split())))
+ self.assertFalse(summary.endswith("\u200d"))
+ summary.encode("utf-8")
+ self.assertRegex(
+ self.css,
+ r"\.campus-details\s+dd\s*\{[^}]*overflow-wrap:\s*anywhere",
+ )
+ self.assertRegex(
+ self.css,
+ r"@media\s*\(max-width:\s*560px\)[\s\S]*?\.campus-project-detail",
+ )
+
+ def test_err03_existing_empty_cap_focus_escape_and_read_only_contracts_remain(self):
+ now = datetime(2026, 8, 14, 12, 0, tzinfo=timezone.utc)
+ zone = self.campus.DEPARTMENT_ZONES["development"]
+ events = []
+ for index in range(4):
+ events.append({
+ "event_id": f"evt-regression-{index}",
+ "task_id": f"task-regression-{index}",
+ "department_id": "development",
+ "department_label": zone["label"],
+ "project": "MY DICTIONARY",
+ "agent_id": f"agent-regression-{index}",
+ "role": zone["roles"][0],
+ "status": "active",
+ "updated_at": "2026-08-14T11:59:00Z",
+ "next_step": "Review safe evidence",
+ "evidence_count": 1,
+ "ephemeral": True,
+ "zone_id": zone["zone_id"],
+ })
+ capped = self.campus.department_campus_projection(events, now=now)
+ empty = self.campus.department_campus_projection([], now=now)
+ self.assertEqual(capped["visible_task_count"], 3)
+ self.assertEqual(capped["omitted_task_count"], 1)
+ self.assertEqual(empty["state"], "empty")
+ self.assertEqual(empty["events"], [])
+
+ campus_script = self._campus_script()
+ self.assertIn("Escape", campus_script)
+ self.assertRegex(
+ campus_script,
+ r"data-campus-project-folder[\s\S]{0,5000}Escape[\s\S]{0,800}\.focus\(\)",
+ )
+ self.assertNotRegex(
+ self.html,
+ r"(?i)<(?:form|input|textarea|select)\b|contenteditable\s*=",
+ )
+ self.assertNotRegex(
+ campus_script,
+ r"(?i)\bmethod\s*:\s*['\"](?:POST|PUT|PATCH|DELETE)['\"]",
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()