Skip to content

Commit 8bfff46

Browse files
authored
Merge pull request #103 from bbartling/chore/openclaw-auth-preflight-tooling
Chore/openclaw auth preflight tooling
2 parents f4b45cb + b33b44d commit 8bfff46

9 files changed

Lines changed: 517 additions & 9 deletions

File tree

openclaw/HANDOFF_PROTOCOL.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,17 @@ So the loop is **mailbox-style**: `issues_log.md` is the inbox/outbox; log files
5252
| Who | Role |
5353
|-----|------|
5454
| **You (human)** | Run gateway, paste prompts, commit/push, decide when to escalate |
55-
| **Cursor / “senior”** | Architecture, code fixes, script changes, doc structure, triage of hard failures |
56-
| **OpenClaw / “junior”** | Execute repeatable commands, capture logs, file issues, small safe edits you allow |
55+
| **Cursor / “senior”** | Architecture, Open-FDD product fixes, code-path hardening, commit SHAs for retest |
56+
| **OpenClaw / tester** | Execute repeatable bench checks, capture logs, file/comment on issues, maintain OpenClaw-side tooling/context |
57+
58+
## Current GitHub contract (2026-03-27)
59+
60+
Use GitHub as the handoff surface.
61+
62+
- **Cursor agents** provide: commit SHA + issue IDs + explicit acceptance criteria.
63+
- **OpenClaw** responds with: pass/fail, exact query/payload, expected vs actual, UTC timestamps, bench URLs/context, and harness-drift vs product-defect classification.
64+
- **OpenClaw does not edit product code** during this loop unless the human explicitly changes that rule.
65+
- **Cursor does not assume parity failures are product bugs** until OpenClaw confirms healthy auth preflight and bench context.
5766

5867
## Failure classification required in handoffs
5968

openclaw/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ OpenClaw may run on a different machine than Open-FDD. For split setup details s
2323
## Current bench reality (keep this straight)
2424

2525
- Bench/frontend/backend/BACnet reachability can be healthy while auth context is still wrong.
26-
- Current direct authenticated check has failed with `FORBIDDEN: Invalid API key`.
27-
- Treat this as **launcher/env/runtime-context drift** unless proven otherwise.
26+
- Missing or invalid `OFDD_API_KEY` should be treated as **launcher/env/runtime-context drift** unless proven otherwise.
27+
- If Open-FDD is running on another machine, load the active `.env` into the shell or point `OPENCLAW_STACK_ENV` at it before calling auth-sensitive APIs.
2828
- **Do not delete or bury issue `#92`**; keep it as likely real product parity tracking once auth is healthy.
2929
- Do not frame auth-context drift itself as a confirmed product bug without clean repro under known-good auth.
3030

openclaw/SKILL.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ Do not blend security hardening notes into unrelated defect triage without clear
7777
- Do not treat coding changes as the primary goal.
7878
- Keep issue/evidence trail in `openclaw/issues_log.md`.
7979
- Use `openclaw/HANDOFF_PROTOCOL.md` for Cursor/OpenClaw handoff discipline.
80+
- In the current operating model: **Cursor = product engineer, OpenClaw = tester**.
81+
- When Cursor provides a commit SHA + issue IDs + acceptance criteria, retest the live bench against that SHA, do **no product-code edits**, and post evidence back to GitHub.
82+
- Healthy auth preflight is required before drawing frontend/API parity conclusions.
8083

8184
## References to read first
8285

openclaw/bench/e2e/2_sparql_crud_and_frontend_test.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -158,14 +158,17 @@ def _load_env_file(path: str) -> None:
158158

159159
def _load_stack_env() -> None:
160160
repo_root = SCRIPT_DIR.parent.parent.parent
161-
candidate_envs = [
162-
repo_root / "stack" / ".env",
163-
Path(os.getcwd()) / ".env",
164-
SCRIPT_DIR / ".env",
165-
]
166161
extra = os.environ.get("OPENCLAW_STACK_ENV", "").strip()
162+
candidate_envs: list[Path] = []
167163
if extra:
168164
candidate_envs.append(Path(extra))
165+
candidate_envs.extend(
166+
[
167+
repo_root / "stack" / ".env",
168+
Path(os.getcwd()) / ".env",
169+
SCRIPT_DIR / ".env",
170+
]
171+
)
169172
home_stack = Path.home() / ".openclaw" / "workspace" / "open-fdd" / "stack" / ".env"
170173
candidate_envs.append(home_stack)
171174
for env_path in candidate_envs:
@@ -179,6 +182,37 @@ def _load_stack_env() -> None:
179182
# Mutable container so main() can set timeout without `global` (SPARQL on large graphs can exceed 30s).
180183
_HTTP_TIMEOUT_SEC: dict[str, float] = {"sec": 120.0}
181184

185+
def _auth_preflight(api_url: str) -> tuple[bool, str | None]:
186+
"""Fail fast when backend auth is enabled but the harness has no usable key."""
187+
code, _, err = _request(api_url, "GET", "/sites")
188+
if code == 200:
189+
if API_KEY:
190+
print("Auth preflight: OK (authenticated backend access available).")
191+
else:
192+
print("Auth preflight: OK (backend accepted anonymous access).")
193+
return True, None
194+
if code == 401:
195+
if API_KEY:
196+
return (
197+
False,
198+
"Auth preflight FAIL — backend rejected OFDD_API_KEY (401 Unauthorized). "
199+
"Treat this as auth/runtime-context drift before triaging product bugs.",
200+
)
201+
return (
202+
False,
203+
"Auth preflight FAIL — backend requires OFDD_API_KEY but none was loaded. "
204+
"Load stack/.env or set OFDD_API_KEY (for split setups you can point OPENCLAW_STACK_ENV at the active .env).",
205+
)
206+
if code == 403:
207+
return (
208+
False,
209+
"Auth preflight FAIL — backend rejected OFDD_API_KEY (403 Forbidden). "
210+
"Treat this as auth/runtime-context drift before triaging product bugs.",
211+
)
212+
if code == 0:
213+
return False, f"Auth preflight FAIL — transport error while checking /sites: {err}"
214+
return False, f"Auth preflight FAIL — GET /sites -> {code}{f' — {err}' if err else ''}"
215+
182216

183217
def _format_api_error(body: dict | list | None, fallback_text: str = "") -> str:
184218
"""Extract human message from Open-FDD JSON error body or raw text."""

openclaw/bench/e2e/4_hot_reload_test.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ def _load_env_file(path: str) -> None:
5757

5858

5959
def _load_stack_env() -> None:
60+
extra = os.environ.get("OPENCLAW_STACK_ENV", "").strip()
61+
if extra:
62+
_load_env_file(extra)
6063
_load_env_file(str(REPO_ROOT / "stack" / ".env"))
6164
_load_env_file(os.path.join(os.getcwd(), ".env"))
6265
_load_env_file(str(SCRIPT_DIR / ".env"))
@@ -67,6 +70,38 @@ def _load_stack_env() -> None:
6770
API_KEY = os.environ.get("OFDD_API_KEY", "").strip()
6871

6972

73+
def _auth_preflight(api_url: str) -> tuple[bool, str | None]:
74+
code, body = _request(api_url, "GET", "/sites")
75+
if code == 200:
76+
if API_KEY:
77+
print("Auth preflight: OK (authenticated backend access available).")
78+
else:
79+
print("Auth preflight: OK (backend accepted anonymous access).")
80+
return True, None
81+
detail = ""
82+
if isinstance(body, dict):
83+
detail = str(body.get("detail") or body.get("message") or "").strip()
84+
if code == 401:
85+
if API_KEY:
86+
return (
87+
False,
88+
"Auth preflight FAIL — backend rejected OFDD_API_KEY (401 Unauthorized). "
89+
"Treat this as auth/runtime-context drift before triaging product bugs.",
90+
)
91+
return (
92+
False,
93+
"Auth preflight FAIL — backend requires OFDD_API_KEY but none was loaded. "
94+
"Load stack/.env or set OFDD_API_KEY (for split setups you can point OPENCLAW_STACK_ENV at the active .env).",
95+
)
96+
if code == 403:
97+
return (
98+
False,
99+
"Auth preflight FAIL — backend rejected OFDD_API_KEY (403 Forbidden). "
100+
"Treat this as auth/runtime-context drift before triaging product bugs.",
101+
)
102+
return False, f"Auth preflight FAIL — GET /sites -> {code}{f' — {detail}' if detail else ''}"
103+
104+
70105
def _request(api_url: str, method: str, path: str, json_body: dict | None = None) -> tuple[int, dict | list]:
71106
try:
72107
import httpx
@@ -327,6 +362,11 @@ def main() -> int:
327362
else:
328363
print(f"API URL: {api_url}")
329364

365+
auth_ok, auth_err = _auth_preflight(api_url)
366+
if not auth_ok:
367+
print(auth_err, file=sys.stderr)
368+
return 1
369+
330370
site_id_for_faults = None
331371
if verify_faults:
332372
site_id_for_faults = _resolve_site_identifier(api_url, site_hint)

0 commit comments

Comments
 (0)