test(web): qualify superseded admission and diagnose narrow table access

Close the missing third-organisation and real shell SIGTERM evidence without expanding product scope. Retain measured narrow-table clipping and accessible-name evidence so follow-up repairs preserve Android vertical gesture ownership rather than assuming horizontal scroll access.

Validation: eight Chromium admission cases, two local-backend narrow socket/access runs, real Compose/Playwright SIGTERM cleanup, twelve focused shell tests, and diff checks passed. No installed release or physical-device qualification.
Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-09-05 17:57:42 +01:00
parent 76fd854da1
commit 0833e8dc80
10 changed files with 772 additions and 12 deletions
+32 -12
View File
@@ -1,5 +1,5 @@
// Full application, real organisation selector; synthetic HTTP and cold socket.
// Run with PULSE_PROOF_WIDTH=390|1440 and PULSE_PROOF_MODE=failure|success|reverse.
// Run with PULSE_PROOF_WIDTH=390|1440 and PULSE_PROOF_MODE=failure|success|reverse|superseded.
// pulse-heavy-run -- node scripts/check-navigation-admission-race.mjs
import { createServer } from "../frontend-modern/node_modules/vite/dist/node/index.js";
import {
@@ -18,9 +18,9 @@ const height = width === 390 ? 844 : 900;
const mode = process.env.PULSE_PROOF_MODE || "failure";
if (
![390, 1440].includes(width) ||
!["failure", "success", "reverse"].includes(mode)
!["failure", "success", "reverse", "superseded"].includes(mode)
) {
throw new Error("Use width 390 or 1440 and mode failure, success or reverse");
throw new Error("Use width 390 or 1440 and mode failure, success, reverse or superseded");
}
const phase =
process.env.PULSE_PROOF_PHASE === "baseline" ? "baseline" : "repaired";
@@ -95,6 +95,10 @@ try {
outgoing = route;
return;
}
if (mode === "superseded") {
if (!incoming) { incoming = route; return; }
return json(emptyAdmission);
}
incoming = route;
if (mode === "reverse") return;
if (mode === "success") return json(emptyAdmission);
@@ -110,6 +114,7 @@ try {
return json([
{ id: "default", displayName: "Default Organization" },
{ id: "acme", displayName: "Acme" },
{ id: "third", displayName: "Third" },
]);
if (path === "/api/license/runtime-capabilities")
return json({
@@ -147,7 +152,17 @@ try {
await org.selectOption("acme");
await expect(org).toHaveValue("acme");
await expect.poll(() => Boolean(incoming)).toBe(true);
if (mode !== "reverse") {
const expectedRequests = ["default", "default", "acme"];
if (mode === "superseded") {
const thirdResponse = page.waitForResponse(r =>
r.url().includes("/api/resources?page=1&limit=1") &&
r.request().headers()["x-pulse-org-id"] === "third");
await org.selectOption("third");
await (await thirdResponse).finished();
await expect(org).toHaveValue("third");
expectedRequests.push("third");
}
if (mode !== "reverse" && mode !== "superseded") {
await expect
.poll(() =>
incoming
@@ -197,18 +212,23 @@ try {
);
// Remove the transient switch toast so the narrow navigation is inspectable.
const dismiss = page.getByRole("button", { name: "Dismiss notification" });
for (const button of await dismiss.all()) await button.click();
await expect(dismiss).toHaveCount(0);
// Multiple switches animate/remove toasts; wait for their normal expiry rather
// than retaining locators that can detach while Playwright waits for stability.
await expect(dismiss).toHaveCount(0, { timeout: 15_000 });
await page.screenshot({ path: `${output}/after-outgoing-response.png` });
await expect(platform).toHaveCount(0);
expect(requests).toEqual(["default", "default", "acme"]);
expect(requests).toEqual(expectedRequests);
expect(failed).toBe(mode === "failure" ? 1 : 0);
if (mode === "reverse") {
if (mode === "reverse" || mode === "superseded") {
const newestResponse = page.waitForResponse(
(r) => r.request() === incoming.request(),
);
await incoming.fulfill({ json: emptyAdmission });
await incoming.fulfill({ json: mode === "superseded"
? { aggregations: { platformAdmission: { proxmox: true, docker: true } } }
: emptyAdmission });
await (await newestResponse).finished();
await page.evaluate(() => new Promise(resolve =>
requestAnimationFrame(() => requestAnimationFrame(resolve))));
await expect(platform).toHaveCount(0);
}
if (width < 1280) {
@@ -235,7 +255,7 @@ try {
hold = false;
await org.selectOption("default");
await expect(platform).toBeVisible();
expect(requests).toEqual(["default", "default", "acme", "default"]);
expect(requests).toEqual([...expectedRequests, "default"]);
if (width < 1280) {
await platform.click();
const menu = page.getByRole("menu", {
@@ -254,9 +274,9 @@ try {
"websocket_reconnected",
),
);
await expect.poll(() => requests.length).toBe(5);
await expect.poll(() => requests.length).toBe(expectedRequests.length + 2);
await expect(platform).toBeVisible();
expect(requests).toEqual(["default", "default", "acme", "default", "default"]);
expect(requests).toEqual([...expectedRequests, "default", "default"]);
expect(await page.evaluate(() => performance.timeOrigin)).toBe(
documentIdentity,
);
@@ -122,6 +122,46 @@ for (const admissionFailure of [false, true]) {
if (width === 390) await mobileDestinations(page);
expect(await page.evaluate(() => performance.timeOrigin)).toBe(documentIdentity);
await capture('recovered');
if (width === 390 && !admissionFailure && process.env.PULSE_E2E_TABLE_ACCESS === '1') {
const row = page.locator('tr[data-docker-container-row]').filter({ hasText: 'notification' }).first();
const table = row.locator('xpath=ancestor::table');
const wrapper = table.locator('..');
const measure = () => wrapper.evaluate(el => ({
width: el.clientWidth, scrollWidth: el.scrollWidth, left: el.scrollLeft,
overflowX: getComputedStyle(el).overflowX, tabIndex: (el as HTMLElement).tabIndex,
cells: Array.from(el.querySelector('tr[data-docker-container-row]')!.children).map(cell => ({
text: cell.textContent, x: cell.getBoundingClientRect().x,
width: cell.getBoundingClientRect().width,
titles: Array.from(cell.querySelectorAll('[title]')).map(n => n.getAttribute('title')),
})),
}));
await row.scrollIntoViewIfNeeded();
const initial = await measure();
await row.hover();
await page.mouse.wheel(2000, 0);
await page.waitForTimeout(350);
const pointer = await measure();
await capture('table-after-horizontal-wheel');
await wrapper.evaluate(el => { el.scrollLeft = 0; });
// Start on a real focusable descendant, without adding tabindex to the UI.
const toggle = row.getByRole('button').first();
await toggle.focus();
await page.keyboard.press('ArrowRight');
await page.waitForTimeout(350);
const keyboard = await measure();
const accessibleRow = await row.ariaSnapshot();
await page.keyboard.press('Enter');
await capture('table-keyboard-detail');
const expanded = await toggle.getAttribute('aria-expanded');
await expect(toggle).toHaveAttribute('aria-expanded', 'true');
const detailId = await toggle.getAttribute('aria-controls');
const detailText = detailId ? await page.locator(`[id="${detailId}"]`).innerText() : null;
await testInfo.attach('narrow-table-access', {
body: JSON.stringify({ initial, pointer, keyboard, accessibleRow, expanded, detailText }, null, 2),
contentType: 'application/json',
});
}
await testInfo.attach('environment', { body: JSON.stringify({ browser: browser.version(), width, height: width === 390 ? 844 : 900, zoom: 1, admissionFailure, failedAdmissions, before }), contentType: 'application/json' });
});
}
@@ -0,0 +1,79 @@
# Access and interruption qualification — 5 September 2026
Base: `76fd854da1b42402ad742258297ff31461f1bb39`, plus accompanying test-only
changes. Chromium 141.0.7390.37; local synthetic inventory. No product change.
## Access diagnosis
Fresh external reference: https://www.w3.org/WAI/WCAG22/Understanding/reflow.html
(accessed September 5). W3C permits two-dimensional data-table scrolling but
not indiscriminate loss of information. This challenges treating a clipped
screenshot as either an automatic failure or a mobile pass. It supports access
diagnosis, not demand for a new surface or a conformance assertion.
At 390×844, Docker's wrapper measures 362px client/scroll width, uses
`overflow-x: clip`, and remains at scrollLeft 0 after horizontal wheel input
and ArrowRight from an existing detail-toggle button. The last update cell is
48.44px wide; its badge is visibly clipped in both viewed fresh wheel screenshots.
This is not off-screen content recoverable by horizontal scrolling. Source
`frontend-modern/src/index.css` applies `overflow: clip` to phone platform
wrappers below 40rem. Do not simply restore nested scrolling: that rule addresses
Android vertical gesture ownership, which needs regression protection.
The first-row accessibility snapshot retains the full update action despite
badge clipping. A second run selects truncated `notifications-worker`: its
accessibility snapshot retains the full name, Enter sets expanded=true, and
rendered detail text includes the full hostname. The viewed detail screenshot
still truncates the drawer heading. This proves accessible naming and keyboard
expansion, not screen-reader/device testing or sighted access to every long value;
scrolling to the identity field was not exercised. Pointer input here means
desktop Chromium horizontal wheel, not Android touch.
Text receipts are human-readable reporter extracts, not JSON. Cell geometry
samples the first container row even in the long-name run; accessibleRow and
detailText describe the selected long-name row. The opt-in diagnostic does not
assert that clipping is acceptable.
## Real full-stack interruption
`run-tests.sh multi-tenant` source-built mock and e2e_runtime Docker images and
started isolated Compose. The watcher waited for an owned cookie file and live
Chromium descendants, recorded project container inventory, then sent SIGTERM
to the supervisor. `interruption.json`: exit 143; server/mock running and seed
exited before the signal; no owned containers, volumes, sampled descendant PIDs
or auth/report/result directories afterwards. No cookie contents retained.
An initial watcher did not recognise headless_shell; that run completed normally
(six passes, one expected skip), so it was not interruption proof. The corrected
run supplies the receipt. The reproduction watcher also explicitly fails if
criteria are unmet. SIGINT/SIGHUP have existing stub coverage only. SIGKILL/host
failure, every possible new descendant and preservation of unrelated stacks
were not qualified. No shared runtime was interrupted or deployed.
## Reproduction and checks
From repository root, install locked frontend-modern and tests/integration
packages and pinned Chromium:
```sh
pulse-heavy-run -- python3 tests/qualification/navigation-reconnect/2026-09-05-access/interrupt.py
pulse-heavy-run -- env PULSE_E2E_USE_LOCAL_BACKEND=1 PULSE_E2E_SKIP_PLAYWRIGHT_INSTALL=1 PULSE_MOCK_MODE=true PULSE_E2E_NAVIGATION_RECOVERY=1 PULSE_E2E_TABLE_ACCESS=1 PULSE_E2E_LOCAL_BACKEND_PORT=18765 npm --prefix tests/integration test -- tests/96-navigation-socket-recovery.spec.ts --project=chromium --grep '390px.*false'
pulse-heavy-run -- bash -c 'set -e; for width in 390 1440; do for mode in failure success reverse superseded; do PULSE_PROOF_WIDTH=$width PULSE_PROOF_MODE=$mode node scripts/check-navigation-admission-race.mjs; done; done'
node --test tests/integration/scripts/run-tests-interruption.test.mjs tests/integration/scripts/run-tests-cleanup.test.mjs
```
Both narrow backend runs passed and stopped; both recorded backend PIDs were
absent afterwards. Twelve focused shell/cleanup tests passed. No full repository
suite. Admission results are retained separately. Multiple-switch toast
locators initially detached; the harness now waits for normal expiry instead
of clicking a changing list. This is a harness correction, not a product fix.
## Next maintenance
Preserve supersession and real interruption coverage. Investigate fitting phone
update actions into allocated cells while retaining accessible naming, keyboard
access and Android page-owned vertical scrolling. Check demand-ledger scope
before product work; this pass introduced no bet or user-visible surface.
Installed alert delivery, release acceptance and physical-device accessibility
remain separate dependencies. Full-stack SIGTERM proof is no longer missing;
no Docker blocker is established.
@@ -0,0 +1,164 @@
[
{
"result": "passed",
"browser": "141.0.7390.37",
"viewport": {
"width": 390,
"height": 844
},
"mode": "failure",
"phase": "repaired",
"requests": [
"default",
"default",
"acme",
"default",
"default"
],
"failed": 1,
"verified_at": "2026-09-05T16:54:49.496Z",
"source_sha256": "d1de019dad5bff7fcb78116a8cf53663f1ddcd4c631bdbf0260b68580ebe0eb7"
},
{
"result": "passed",
"browser": "141.0.7390.37",
"viewport": {
"width": 390,
"height": 844
},
"mode": "success",
"phase": "repaired",
"requests": [
"default",
"default",
"acme",
"default",
"default"
],
"failed": 0,
"verified_at": "2026-09-05T16:55:12.726Z",
"source_sha256": "d1de019dad5bff7fcb78116a8cf53663f1ddcd4c631bdbf0260b68580ebe0eb7"
},
{
"result": "passed",
"browser": "141.0.7390.37",
"viewport": {
"width": 390,
"height": 844
},
"mode": "reverse",
"phase": "repaired",
"requests": [
"default",
"default",
"acme",
"default",
"default"
],
"failed": 0,
"verified_at": "2026-09-05T16:55:35.941Z",
"source_sha256": "d1de019dad5bff7fcb78116a8cf53663f1ddcd4c631bdbf0260b68580ebe0eb7"
},
{
"result": "passed",
"browser": "141.0.7390.37",
"viewport": {
"width": 390,
"height": 844
},
"mode": "superseded",
"phase": "repaired",
"requests": [
"default",
"default",
"acme",
"third",
"default",
"default"
],
"failed": 0,
"verified_at": "2026-09-05T16:55:59.091Z",
"source_sha256": "d1de019dad5bff7fcb78116a8cf53663f1ddcd4c631bdbf0260b68580ebe0eb7"
},
{
"result": "passed",
"browser": "141.0.7390.37",
"viewport": {
"width": 1440,
"height": 900
},
"mode": "failure",
"phase": "repaired",
"requests": [
"default",
"default",
"acme",
"default",
"default"
],
"failed": 1,
"verified_at": "2026-09-05T16:56:19.211Z",
"source_sha256": "d1de019dad5bff7fcb78116a8cf53663f1ddcd4c631bdbf0260b68580ebe0eb7"
},
{
"result": "passed",
"browser": "141.0.7390.37",
"viewport": {
"width": 1440,
"height": 900
},
"mode": "success",
"phase": "repaired",
"requests": [
"default",
"default",
"acme",
"default",
"default"
],
"failed": 0,
"verified_at": "2026-09-05T16:56:42.832Z",
"source_sha256": "d1de019dad5bff7fcb78116a8cf53663f1ddcd4c631bdbf0260b68580ebe0eb7"
},
{
"result": "passed",
"browser": "141.0.7390.37",
"viewport": {
"width": 1440,
"height": 900
},
"mode": "reverse",
"phase": "repaired",
"requests": [
"default",
"default",
"acme",
"default",
"default"
],
"failed": 0,
"verified_at": "2026-09-05T16:57:00.634Z",
"source_sha256": "d1de019dad5bff7fcb78116a8cf53663f1ddcd4c631bdbf0260b68580ebe0eb7"
},
{
"result": "passed",
"browser": "141.0.7390.37",
"viewport": {
"width": 1440,
"height": 900
},
"mode": "superseded",
"phase": "repaired",
"requests": [
"default",
"default",
"acme",
"third",
"default",
"default"
],
"failed": 0,
"verified_at": "2026-09-05T16:57:19.715Z",
"source_sha256": "d1de019dad5bff7fcb78116a8cf53663f1ddcd4c631bdbf0260b68580ebe0eb7"
}
]
@@ -0,0 +1,38 @@
import subprocess, time, re, pathlib, signal, json, os
root=pathlib.Path.cwd(); out=root/'tmp/web-followup'; out.mkdir(parents=True, exist_ok=True); log=out/'interruption.log'
with log.open('w') as f:
p=subprocess.Popen(['bash','tests/integration/scripts/run-tests.sh','multi-tenant'],stdout=f,stderr=subprocess.STDOUT)
run=None; before=None; interrupted=False
try:
deadline=time.time()+1200
while p.poll() is None and time.time()<deadline:
data=log.read_text(); m=re.search(r'Isolated integration project: (pulse-e2e-[a-f0-9]+)',data)
if m: run=m[1]
if run:
auth=root/'tests/integration/tmp/playwright-auth'/run/'shared-cookie-session.json'
procs=subprocess.check_output(['ps','-eo','pid,ppid,args'],text=True)
# Require browser descendant and completed cookie bootstrap before signalling.
descendants={p.pid}; rows=[]
for line in procs.splitlines()[1:]:
fields=line.strip().split(None,2)
if len(fields)==3: rows.append((int(fields[0]),int(fields[1]),fields[2]))
for _ in range(12):
descendants.update(pid for pid,ppid,args in rows if ppid in descendants)
browsers=[pid for pid,ppid,args in rows if pid in descendants and ('headless_shell' in args or '/chrome ' in args)]
if auth.exists() and browsers:
before=subprocess.check_output(['docker','ps','-a','--filter',f'label=com.docker.compose.project={run}','--format','{{.Names}} {{.State}}'],text=True)
if 'running' in before:
p.send_signal(signal.SIGTERM); interrupted=True; break
time.sleep(.2)
if not interrupted and p.poll() is None: p.send_signal(signal.SIGTERM)
code=p.wait(timeout=60)
after=subprocess.check_output(['docker','ps','-a','--filter',f'label=com.docker.compose.project={run}','--format','{{.Names}}'],text=True) if run else 'no run'
volumes=subprocess.check_output(['docker','volume','ls','--filter',f'label=com.docker.compose.project={run}','--format','{{.Name}}'],text=True) if run else 'no run'
remaining=[pid for pid in descendants if pathlib.Path(f'/proc/{pid}').exists()] if interrupted else []
paths=[root/'tests/integration'/part/run for part in ['tmp/playwright-auth','test-results','playwright-report']] if run else []
result=dict(run=run,interrupted=interrupted,exit=code,containers_before=before,containers_after=after,volumes_after=volumes,remaining_pids=remaining,owned_paths_remaining=[str(x.relative_to(root)) for x in paths if x.exists()])
(out/'interruption.json').write_text(json.dumps(result,indent=2)+'\n'); print(json.dumps(result,indent=2))
assert interrupted and code == 143, 'Did not interrupt authenticated Chromium'
assert not after and not volumes and not remaining and not result['owned_paths_remaining'], 'Owned resources remain'
finally:
if p.poll() is None: p.send_signal(signal.SIGTERM); p.wait(timeout=60)
@@ -0,0 +1,10 @@
{
"run": "pulse-e2e-c95ed0849f86ed9f8e32a964b082d150",
"interrupted": true,
"exit": 143,
"containers_before": "pulse-e2e-c95ed0849f86ed9f8e32a964b082d150-server running\npulse-e2e-c95ed0849f86ed9f8e32a964b082d150-seed exited\npulse-e2e-c95ed0849f86ed9f8e32a964b082d150-mock running\n",
"containers_after": "",
"volumes_after": "",
"remaining_pids": [],
"owned_paths_remaining": []
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

@@ -0,0 +1,168 @@
{
"initial": {
"width": 362,
"scrollWidth": 362,
"left": 0,
"overflowX": "clip",
"tabIndex": -1,
"cells": [
{
"text": "loki",
"x": 9,
"width": 108.59375,
"titles": [
"Expand details for loki",
"Healthy",
"loki"
]
},
{
"text": "running",
"x": 117.59375,
"width": 48.4375,
"titles": [
"running"
]
},
{
"text": "6%",
"x": 166.03125,
"width": 55.890625,
"titles": []
},
{
"text": "74%",
"x": 221.921875,
"width": 63.34375,
"titles": []
},
{
"text": "2",
"x": 285.265625,
"width": 37.25,
"titles": []
},
{
"text": "Update",
"x": 322.515625,
"width": 48.4375,
"titles": []
}
]
},
"pointer": {
"width": 362,
"scrollWidth": 362,
"left": 0,
"overflowX": "clip",
"tabIndex": -1,
"cells": [
{
"text": "loki",
"x": 9,
"width": 108.59375,
"titles": [
"Expand details for loki",
"Healthy",
"loki"
]
},
{
"text": "running",
"x": 117.59375,
"width": 48.4375,
"titles": [
"running"
]
},
{
"text": "6%",
"x": 166.03125,
"width": 55.890625,
"titles": []
},
{
"text": "74%",
"x": 221.921875,
"width": 63.34375,
"titles": []
},
{
"text": "2",
"x": 285.265625,
"width": 37.25,
"titles": []
},
{
"text": "Update",
"x": 322.515625,
"width": 48.4375,
"titles": []
}
]
},
"keyboard": {
"width": 362,
"scrollWidth": 362,
"left": 0,
"overflowX": "clip",
"tabIndex": -1,
"cells": [
{
"text": "loki",
"x": 9,
"width": 108.59375,
"titles": [
"Expand details for loki",
"Healthy",
"loki"
]
},
{
"text": "running",
"x": 117.59375,
"width": 48.4375,
"titles": [
"running"
]
},
{
"text": "6%",
"x": 166.03125,
"width": 55.890625,
"titles": []
},
{
"text": "74%",
"x": 221.921875,
"width": 63.34375,
"titles": []
},
{
"text": "2",
"x": 285.265625,
"width": 37.25,
"titles": []
},
{
"text": "Update",
"x": 322.515625,
"width": 48.4375,
"titles": []
}
]
},
"accessibleRow": "- 'row "Expand details for loki loki running 6% 74% 2 Click to review and update Current: sha256:afe3f... Latest: sha256:2a043..."':
- cell "Expand details for loki loki":
- button "Expand details for loki"
- text: loki
- cell "running"
- cell "6%"
- cell "74%"
- cell "2"
- 'cell "Click to review and update Current: sha256:afe3f... Latest: sha256:2a043..."':
- 'button "Click to review and update Current: sha256:afe3f... Latest: sha256:2a043..."':
- img
- text: Update",
"expanded": "true"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

@@ -0,0 +1,241 @@
{
"initial": {
"width": 362,
"scrollWidth": 362,
"left": 0,
"overflowX": "clip",
"tabIndex": -1,
"cells": [
{
"text": "loki",
"x": 9,
"width": 108.59375,
"titles": [
"Expand details for loki",
"Healthy",
"loki"
]
},
{
"text": "running",
"x": 117.59375,
"width": 48.4375,
"titles": [
"running"
]
},
{
"text": "3%",
"x": 166.03125,
"width": 55.890625,
"titles": []
},
{
"text": "74%",
"x": 221.921875,
"width": 63.34375,
"titles": []
},
{
"text": "3",
"x": 285.265625,
"width": 37.25,
"titles": []
},
{
"text": "Current",
"x": 322.515625,
"width": 48.4375,
"titles": []
}
]
},
"pointer": {
"width": 362,
"scrollWidth": 362,
"left": 0,
"overflowX": "clip",
"tabIndex": -1,
"cells": [
{
"text": "loki",
"x": 9,
"width": 108.59375,
"titles": [
"Expand details for loki",
"Healthy",
"loki"
]
},
{
"text": "running",
"x": 117.59375,
"width": 48.4375,
"titles": [
"running"
]
},
{
"text": "3%",
"x": 166.03125,
"width": 55.890625,
"titles": []
},
{
"text": "74%",
"x": 221.921875,
"width": 63.34375,
"titles": []
},
{
"text": "3",
"x": 285.265625,
"width": 37.25,
"titles": []
},
{
"text": "Current",
"x": 322.515625,
"width": 48.4375,
"titles": []
}
]
},
"keyboard": {
"width": 362,
"scrollWidth": 362,
"left": 0,
"overflowX": "clip",
"tabIndex": -1,
"cells": [
{
"text": "loki",
"x": 9,
"width": 108.59375,
"titles": [
"Expand details for loki",
"Healthy",
"loki"
]
},
{
"text": "running",
"x": 117.59375,
"width": 48.4375,
"titles": [
"running"
]
},
{
"text": "3%",
"x": 166.03125,
"width": 55.890625,
"titles": []
},
{
"text": "74%",
"x": 221.921875,
"width": 63.34375,
"titles": []
},
{
"text": "3",
"x": 285.265625,
"width": 37.25,
"titles": []
},
{
"text": "Current",
"x": 322.515625,
"width": 48.4375,
"titles": []
}
]
},
"accessibleRow": "- row "Expand details for notifications-worker notifications-worker running 4% 49% 1 —":
- cell "Expand details for notifications-worker notifications-worker":
- button "Expand details for notifications-worker"
- text: notifications-worker
- cell "running"
- cell "4%"
- cell "49%"
- cell "1"
- cell "—"",
"expanded": "true",
"detailText": "notifications-worker
Container
Docker / Podman
Overview
History
Manage
RUNTIME CONTEXT
Observed state\t
Running
Uptime\t
11d 18h
Last seen\t
11s ago
CONTAINER
Image\t
ghcr.io/pulse-demo/notifications-worker:2026.04
Restarts\t
1
Created\t
17 days ago
Started\t
11 days ago
Compose project\t
orion-2
Compose service\t
postgres
Labels\t
com.docker.compose.project: orion-2
com.docker.compose.service: postgres
com.pulse.demo: true
com.pulse.role.production: true
com.pulse.role.queue: true
com.pulse.role.worker: true
environment: staging
IDENTITY
Hostname\t
notifications-worker
Primary ID\t
app-container:orion-2-26f93461b215
Parent\t
agent-679e9ef0038bd2b7
Discovery\t
app-container:notifications-worker
Metrics Target\t
app-container:orion-2-26f93461b215
Aliases\t
app-container:orion-2-26f93461b215
orion-2-26f93461b215
orion-2-mock
app-platform-01
+4
Relationship map
1 canonical relationship
CANONICAL RELATIONSHIPS
notifications-worker
docker-network-0ab79ab291cd98fa
Attached To
100% confidence · Docker Adapter · last seen just now"
}