Skip to content

Commit 68c1dcd

Browse files
authored
[codex] final closeout hardening (#21)
* fix(host-safety): enforce operator-manual desktop guards * fix(tooling): keep shebang parity in pre-commit * fix(ci): avoid private-key test literal false positive * chore(ci): normalize sensitive gate file endings * chore(security): add detect-secrets baseline * test(ci): avoid typo false positive in ai routing pack * fix(tooling): limit generic hooks to pre-commit * fix(api): terminate process groups before child fallback * fix(ci): allow host fallback in hooks equivalence * fix(ci): unblock PR run and hooks equivalence gates * test(e2e): align smoke navigation semantics
1 parent 1664fd2 commit 68c1dcd

21 files changed

Lines changed: 777 additions & 96 deletions

.github/workflows/desktop-smoke.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ jobs:
2929
SWIFT_BUNDLE_ID_INPUT: ${{ github.event.inputs.swift_bundle_id }}
3030
MATRIX_TARGET: ${{ matrix.target }}
3131
MATRIX_PROFILE: ${{ matrix.profile }}
32+
UIQ_DESKTOP_AUTOMATION_MODE: operator-manual
33+
UIQ_DESKTOP_AUTOMATION_REASON: github-workflow-desktop-smoke
3234
steps:
3335
- uses: ./.github/actions/repo-checkout
3436
- uses: ./.github/actions/setup-deps

.github/workflows/nightly.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,8 @@ jobs:
350350
env:
351351
UIQ_TAURI_APP_PATH: ${{ vars.UIQ_TAURI_APP_PATH || secrets.UIQ_TAURI_APP_PATH || '' }}
352352
UIQ_SWIFT_BUNDLE_ID: ${{ vars.UIQ_SWIFT_BUNDLE_ID || secrets.UIQ_SWIFT_BUNDLE_ID || '' }}
353+
UIQ_DESKTOP_AUTOMATION_MODE: operator-manual
354+
UIQ_DESKTOP_AUTOMATION_REASON: github-workflow-nightly-desktop-regression
353355
steps:
354356
- uses: ./.github/actions/repo-checkout
355357
- uses: ./.github/actions/setup-deps

.github/workflows/weekly.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,8 @@ jobs:
310310
env:
311311
UIQ_TAURI_APP_PATH: ${{ vars.UIQ_TAURI_APP_PATH || secrets.UIQ_TAURI_APP_PATH || '' }}
312312
UIQ_SWIFT_BUNDLE_ID: ${{ vars.UIQ_SWIFT_BUNDLE_ID || secrets.UIQ_SWIFT_BUNDLE_ID || '' }}
313+
UIQ_DESKTOP_AUTOMATION_MODE: operator-manual
314+
UIQ_DESKTOP_AUTOMATION_REASON: github-workflow-weekly-desktop-regression
313315
steps:
314316
- uses: ./.github/actions/repo-checkout
315317
- uses: ./.github/actions/setup-deps

.secrets.baseline

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
{
2+
"version": "1.5.0",
3+
"plugins_used": [
4+
{
5+
"name": "ArtifactoryDetector"
6+
},
7+
{
8+
"name": "AWSKeyDetector"
9+
},
10+
{
11+
"name": "AzureStorageKeyDetector"
12+
},
13+
{
14+
"name": "Base64HighEntropyString",
15+
"limit": 4.5
16+
},
17+
{
18+
"name": "BasicAuthDetector"
19+
},
20+
{
21+
"name": "CloudantDetector"
22+
},
23+
{
24+
"name": "DiscordBotTokenDetector"
25+
},
26+
{
27+
"name": "GitHubTokenDetector"
28+
},
29+
{
30+
"name": "GitLabTokenDetector"
31+
},
32+
{
33+
"name": "HexHighEntropyString",
34+
"limit": 3.0
35+
},
36+
{
37+
"name": "IbmCloudIamDetector"
38+
},
39+
{
40+
"name": "IbmCosHmacDetector"
41+
},
42+
{
43+
"name": "IPPublicDetector"
44+
},
45+
{
46+
"name": "JwtTokenDetector"
47+
},
48+
{
49+
"name": "KeywordDetector",
50+
"keyword_exclude": ""
51+
},
52+
{
53+
"name": "MailchimpDetector"
54+
},
55+
{
56+
"name": "NpmDetector"
57+
},
58+
{
59+
"name": "OpenAIDetector"
60+
},
61+
{
62+
"name": "PrivateKeyDetector"
63+
},
64+
{
65+
"name": "PypiTokenDetector"
66+
},
67+
{
68+
"name": "SendGridDetector"
69+
},
70+
{
71+
"name": "SlackDetector"
72+
},
73+
{
74+
"name": "SoftlayerDetector"
75+
},
76+
{
77+
"name": "SquareOAuthDetector"
78+
},
79+
{
80+
"name": "StripeDetector"
81+
},
82+
{
83+
"name": "TelegramBotTokenDetector"
84+
},
85+
{
86+
"name": "TwilioKeyDetector"
87+
}
88+
],
89+
"filters_used": [
90+
{
91+
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
92+
},
93+
{
94+
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
95+
"min_level": 2
96+
},
97+
{
98+
"path": "detect_secrets.filters.heuristic.is_indirect_reference"
99+
},
100+
{
101+
"path": "detect_secrets.filters.heuristic.is_likely_id_string"
102+
},
103+
{
104+
"path": "detect_secrets.filters.heuristic.is_lock_file"
105+
},
106+
{
107+
"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
108+
},
109+
{
110+
"path": "detect_secrets.filters.heuristic.is_potential_uuid"
111+
},
112+
{
113+
"path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
114+
},
115+
{
116+
"path": "detect_secrets.filters.heuristic.is_sequential_string"
117+
},
118+
{
119+
"path": "detect_secrets.filters.heuristic.is_swagger_file"
120+
},
121+
{
122+
"path": "detect_secrets.filters.heuristic.is_templated_secret"
123+
},
124+
{
125+
"path": "detect_secrets.filters.regex.should_exclude_file",
126+
"pattern": [
127+
"(^|/)\\.codex/|(^|/)docs/|(^|/)artifacts/|(^|/)(tests?|__tests__|fixtures?)/|(^|/)(pnpm-lock\\.yaml|package-lock\\.json|yarn\\.lock|uv\\.lock)$|(^|/)README\\.md$|(^|/)\\.env\\.example$|(^|/)docker-compose\\.ya?ml$|(^|/)configs/env/contract\\.yaml$|(^|/)apps/api/app/core/settings\\.py$|(^|/)scripts/|(^|/)apps/automation-runner/"
128+
]
129+
}
130+
],
131+
"results": {},
132+
"generated_at": "2026-04-07T08:15:08Z"
133+
}

apps/api/app/services/automation_service.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import json
77
import os
88
import re
9+
import signal
910
import subprocess
1011
import logging
1112
import random
@@ -584,17 +585,42 @@ def _terminate_process(
584585
self, process: subprocess.Popen[str], timeout_seconds: float = 3.0
585586
) -> bool:
586587
"""Try terminate first, then force kill the direct child if it does not exit."""
588+
poll = getattr(process, "poll", None)
589+
if callable(poll) and poll() is not None:
590+
return False
587591
pid = self._owned_child_pid(process)
588592
if pid is None:
589593
logger.warning(
590-
"refused to signal process without owned positive pid",
591-
extra={"pid": process.pid},
594+
"missing owned positive pid; falling back to direct child termination",
595+
extra={"pid": getattr(process, "pid", None)},
592596
)
593-
return False
594-
if process.poll() is not None:
595-
return False
597+
try:
598+
process.terminate()
599+
process.wait(timeout=timeout_seconds)
600+
return False
601+
except subprocess.TimeoutExpired:
602+
try:
603+
process.kill()
604+
try:
605+
process.wait(timeout=timeout_seconds)
606+
except subprocess.TimeoutExpired:
607+
logger.warning(
608+
"process did not exit after kill",
609+
extra={"error": "kill timeout"},
610+
)
611+
return True
612+
except OSError as error:
613+
logger.warning(
614+
"failed to kill direct child process",
615+
extra={"error": str(error), "pid": getattr(process, "pid", None)},
616+
)
617+
return False
596618
try:
597619
try:
620+
os.killpg(pid, signal.SIGTERM)
621+
except ProcessLookupError:
622+
return False
623+
except OSError:
598624
process.terminate()
599625
except ProcessLookupError:
600626
return False
@@ -608,6 +634,10 @@ def _terminate_process(
608634
return False
609635
except subprocess.TimeoutExpired:
610636
try:
637+
os.killpg(pid, signal.SIGKILL)
638+
except ProcessLookupError:
639+
return False
640+
except OSError:
611641
process.kill()
612642
except ProcessLookupError:
613643
return False

apps/api/tests/test_backend_coverage_final_sprint.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,16 +220,21 @@ def test_automation_retry_terminate_and_sync_edge_branches(
220220
class TimeoutProcess:
221221
pid = None
222222

223+
def __init__(self) -> None:
224+
self.terminated = False
225+
223226
def terminate(self) -> None:
224-
return None
227+
self.terminated = True
225228

226229
def kill(self) -> None:
227-
return None
230+
self.terminated = True
228231

229232
def wait(self, timeout: float | None = None) -> int:
230233
raise subprocess.TimeoutExpired("cmd", timeout)
231234

232-
assert automation_service._terminate_process(TimeoutProcess(), timeout_seconds=0.01) is True
235+
process = TimeoutProcess()
236+
assert automation_service._terminate_process(process, timeout_seconds=0.01) is True
237+
assert process.terminated is True
233238
assert warnings
234239

235240
now = datetime.now(timezone.utc)

apps/api/tests/test_universal_platform_resume_branches.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def __init__(self, run: RunRecord | None) -> None:
3131
self._lock = RLock()
3232
self.audits: list[tuple[str, str | None, dict[str, object]]] = []
3333
self.last_env: dict[str, str] | None = None
34+
self.materialized_flow_ids: list[str] = []
3435

3536
def get_run(self, run_id: str, requester: str | None = None) -> RunRecord:
3637
if self._run is None or self._run.run_id != run_id:
@@ -55,7 +56,11 @@ def get_template(self, template_id: str, requester: str | None = None) -> Simple
5556

5657
def get_flow(self, flow_id: str, requester: str | None = None) -> SimpleNamespace:
5758
_ = requester
58-
return SimpleNamespace(flow_id=flow_id, start_url="https://example.com/register")
59+
return SimpleNamespace(
60+
flow_id=flow_id,
61+
session_id="session_1",
62+
start_url="https://example.com/register",
63+
)
5964

6065
def _get_validated_params_snapshot(self, run_id: str) -> dict[str, str]:
6166
_ = run_id
@@ -64,6 +69,9 @@ def _get_validated_params_snapshot(self, run_id: str) -> dict[str, str]:
6469
def _validate_params(self, template: object, params: object, otp_policy: object) -> None:
6570
_ = (template, params, otp_policy)
6671

72+
def _materialize_replay_bridge(self, flow: SimpleNamespace) -> None:
73+
self.materialized_flow_ids.append(flow.flow_id)
74+
6775
def _build_env(self, start_url: str, params: dict[str, str], otp_value: str) -> dict[str, str]:
6876
env = {"START_URL": start_url, "EMAIL": params["email"], "OTP": otp_value}
6977
self.last_env = env

apps/automation-runner/scripts/lib/extract-video-flow.ai-routing.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ test("ai-input-pack trims transcript/events/html and summarizes HAR entries", ()
337337
transcript: [
338338
{ t: "0", text: " first line " },
339339
{ t: "1", text: "" },
340-
{ t: "2", text: "second line that is very long" },
340+
{ t: "2", text: "later line that is very long" },
341341
],
342342
events: [
343343
{
@@ -372,7 +372,7 @@ test("ai-input-pack trims transcript/events/html and summarizes HAR entries", ()
372372

373373
assert.equal(packed.payload.transcript.length, 2)
374374
assert.equal(packed.payload.transcript[0]?.text, "first line")
375-
assert.equal(packed.payload.transcript[1]?.text, "secon")
375+
assert.equal(packed.payload.transcript[1]?.text, "later")
376376
assert.equal(packed.payload.eventLog.length, 1)
377377
assert.equal(packed.payload.eventLog[0]?.value, "value")
378378
assert.equal(packed.payload.htmlSnippet, "<div>abc")

apps/web/tests/e2e/smoke.spec.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,9 @@ pwTest.beforeEach(async ({ page }) => {
100100
})
101101
return
102102
}
103-
throw new Error(`[frontend-smoke] Unhandled API route: ${route.request().method()} ${url.pathname}`)
103+
throw new Error(
104+
`[frontend-smoke] Unhandled API route: ${route.request().method()} ${url.pathname}`
105+
)
104106
})
105107
await page.route("**/health/**", async (route) => {
106108
const url = new URL(route.request().url())
@@ -111,7 +113,13 @@ pwTest.beforeEach(async ({ page }) => {
111113
body: JSON.stringify({
112114
uptime_seconds: 120,
113115
task_total: 0,
114-
task_counts: { queued: 0, running: 0, success: 0, failed: 0, cancelled: 0 },
116+
task_counts: {
117+
queued: 0,
118+
running: 0,
119+
success: 0,
120+
failed: 0,
121+
cancelled: 0,
122+
},
115123
metrics: { requests_total: 1, rate_limited: 0 },
116124
}),
117125
})
@@ -131,7 +139,9 @@ pwTest.beforeEach(async ({ page }) => {
131139
})
132140
return
133141
}
134-
throw new Error(`[frontend-smoke] Unhandled health route: ${route.request().method()} ${url.pathname}`)
142+
throw new Error(
143+
`[frontend-smoke] Unhandled health route: ${route.request().method()} ${url.pathname}`
144+
)
135145
})
136146
await page.addInitScript(() => {
137147
window.localStorage.setItem("ab_onboarding_done", "1")
@@ -142,9 +152,16 @@ pwTest.beforeEach(async ({ page }) => {
142152
pwTest("@smoke frontend shell and primary navigation", async ({ page }) => {
143153
await page.goto("/")
144154

155+
const primaryNavigation = page.getByRole("navigation", {
156+
name: "Primary navigation",
157+
})
145158
await expect(page.getByRole("heading", { level: 1, name: "ProofTrail" })).toBeVisible()
146-
await expect(page.getByRole("tablist", { name: "Primary navigation" })).toBeVisible()
147-
await expect(page.getByRole("tab", { name: "Quick Launch" })).toHaveAttribute("aria-selected", "true")
159+
await expect(primaryNavigation).toBeVisible()
160+
await expect(primaryNavigation.getByRole("tablist")).toBeVisible()
161+
await expect(page.getByRole("tab", { name: "Quick Launch" })).toHaveAttribute(
162+
"aria-selected",
163+
"true"
164+
)
148165
await expect(page.getByRole("tablist", { name: "Command categories" })).toBeVisible()
149166
const sidebarToggle = page.getByLabel("Collapse parameter rail")
150167
await expect(sidebarToggle).toHaveAttribute("aria-expanded", "true")
@@ -160,7 +177,7 @@ pwTest("@smoke frontend shell and primary navigation", async ({ page }) => {
160177
await taskCenterTab.click()
161178
await expect(taskCenterTab).toHaveAttribute("aria-selected", "true")
162179
await expect(taskCenterTab).toHaveAttribute("aria-controls", "app-view-tasks-panel")
163-
const taskCenterPanel = page.locator('section#app-view-tasks-panel')
180+
const taskCenterPanel = page.locator("section#app-view-tasks-panel")
164181
await expect(taskCenterPanel).toHaveAttribute("role", "tabpanel")
165182
await expect(taskCenterPanel).toHaveAttribute("aria-labelledby", "console-tab-tasks")
166183

@@ -181,6 +198,8 @@ pwTest("@smoke frontend shell and primary navigation", async ({ page }) => {
181198
await expect(flowDraftTab).toHaveAttribute("aria-selected", "true")
182199
await expect(page.getByRole("heading", { name: "Key outcome and next action" })).toBeVisible()
183200
await expect(
184-
page.getByText("Advanced workshop (optional): system diagnostics, flow editing, and debugging evidence")
201+
page.getByText(
202+
"Advanced workshop (optional): system diagnostics, flow editing, and debugging evidence"
203+
)
185204
).toBeVisible()
186205
})

0 commit comments

Comments
 (0)