Implement bounded browser service, consumer evidence and managed package
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a13590-a16f-7f60-be60-fd71627bc99d
205
docs/consumer-contract.md
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
# Browser job contract v1
|
||||
|
||||
An agent needs Python 3.10+ and the single file `client/rapp_playwright.py`.
|
||||
Playwright and Chromium run at the provider. The service's JSON protocol is
|
||||
versioned independently of Playwright; clients send `X-Rapp-Protocol: 1` and
|
||||
requests contain `api_version: "1"`. A mismatch returns HTTP 409 before a run.
|
||||
|
||||
## Access and first command
|
||||
|
||||
The managed endpoint is private. With a Kubernetes identity allowed to get the
|
||||
playwright pod and create its port-forward:
|
||||
|
||||
```sh
|
||||
kubectl -n playwright port-forward deployment/playwright 18081:8080 --address 127.0.0.1
|
||||
```
|
||||
|
||||
Leave the tunnel running. In another terminal, from this checkout:
|
||||
|
||||
```sh
|
||||
python3 client/rapp_playwright.py preflight
|
||||
python3 client/rapp_playwright.py trial --out .local/trial-01
|
||||
```
|
||||
|
||||
The trial obtains its specification from the service, opens the bundled fixture,
|
||||
checks the rendered counter, presses Tab, asserts button focus, presses Enter,
|
||||
asserts the increment, and saves screenshots at 1280×800 and 390×844. It downloads
|
||||
artifacts and a receipt. Use a **new output directory** for each invocation.
|
||||
This command becomes a live service path only after the deployment exists;
|
||||
see SCOPE.md and retained runtime evidence for actual availability.
|
||||
|
||||
Preflight reports actual browser launch evidence, its timestamp, browser and
|
||||
Playwright versions, release, execution location, allowed target origins,
|
||||
concurrency and retention. The launch probe runs at startup and at most once
|
||||
per minute while idle. It is not an assertion about the consumer's target.
|
||||
HTTP 503 indicates an unavailable browser; connection failure means the service
|
||||
or tunnel is unavailable. It never implies a locally installed browser.
|
||||
|
||||
The service listens on **pod loopback**, with a deny-all ingress policy and no
|
||||
Ingress/NodePort. Kubernetes RBAC/SSH authenticates the tunnel; the service does
|
||||
not provide an unauthenticated public listener or issue platform identities.
|
||||
No shared application password is required. Do not widen the listener to expose
|
||||
it remotely without adding an authenticated transport and reviewing the boundary.
|
||||
|
||||
## Run a consumer scenario
|
||||
|
||||
```sh
|
||||
python3 client/rapp_playwright.py run --spec examples/trial.json --out .local/run-01
|
||||
```
|
||||
|
||||
Example request:
|
||||
|
||||
```json
|
||||
{
|
||||
"api_version": "1",
|
||||
"target": "fixture",
|
||||
"label": "keyboard-smoke",
|
||||
"timeout_ms": 60000,
|
||||
"viewports": [{"name":"desktop","width":1280,"height":800}, {"name":"narrow","width":390,"height":844}],
|
||||
"steps": [
|
||||
{"action":"visible","selector":"h1"},
|
||||
{"action":"press","key":"Tab"},
|
||||
{"action":"focused","selector":"#increment"},
|
||||
{"action":"press","key":"Enter"},
|
||||
{"action":"text","selector":"#count","value":"1"},
|
||||
{"action":"screenshot","name":"keyboard-result"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`target` is `fixture` or an HTTP(S) URL whose exact origin the provider has
|
||||
allowed. Userinfo, query strings and fragments in the initial target are rejected
|
||||
to avoid putting credentials into ordinary receipts. V1 tests public or synthetic
|
||||
pages; it has no credential injection, storage-state import or arbitrary code.
|
||||
Consumers own permission to use targets and handle captured content.
|
||||
|
||||
Steps execute in order in a new browser context for each viewport. Supported
|
||||
actions are `click`, `focus`, `fill`, `press`, `visible`, `text`, `focused`, `url`
|
||||
and `screenshot`. Selectors for `text` are CSS; use CSS consistently across the
|
||||
scenario. `text` compares trimmed text exactly with a five-second retry window.
|
||||
`focused` checks the active element; `url` checks the complete current URL.
|
||||
At least one assertion is required. A failed step stops the run, preserves prior
|
||||
artifacts, and records the viewport, index, action and failure. Each completed
|
||||
viewport also produces a final screenshot. Screenshots are viewport-sized,
|
||||
not full-page captures. No visual-baseline pass is inferred from a saved PNG.
|
||||
|
||||
## HTTP lifecycle
|
||||
|
||||
| Method/path | Result |
|
||||
| --- | --- |
|
||||
| `GET /healthz` | Process liveness only; no protocol header needed |
|
||||
| `GET /v1/preflight` | Launch availability, supported versions, location and limits |
|
||||
| `GET /v1/trial` | Consumer-independent fixture scenario |
|
||||
| `POST /v1/runs` | HTTP 202: run ID, secret capability token, expiry |
|
||||
| `GET /v1/runs/{id}` | Current status or terminal receipt; requires run token |
|
||||
| `DELETE /v1/runs/{id}` | Idempotent cancellation request; requires run token |
|
||||
| `GET /v1/runs/{id}/artifacts/{name}` | Terminal run's named PNG; requires run token |
|
||||
|
||||
Run operations use `Authorization: Bearer <run-token>`. The service stores only
|
||||
the token's SHA-256 hash and has no run-listing endpoint. Wrong/missing tokens,
|
||||
unknown IDs and expired IDs all return 404. Do not publish `run.json`, which is
|
||||
the client's private run handle. Directory permissions are 0700 and files 0600.
|
||||
The client prints no token. Receipts omit fill values, expected page text,
|
||||
cookies, browser state, console output and request headers. Screenshots can still
|
||||
contain sensitive page content; only test appropriate targets and control copies.
|
||||
|
||||
States: `queued → running → passed|failed|cancelled`. A previously nonterminal
|
||||
run found after a service restart becomes `interrupted/service_restarted`, and
|
||||
its uncommitted artifacts are removed. Terminal receipts/artifacts survive a
|
||||
restart when the provider's data volume is retained. V1 provides no automatic
|
||||
retry: a lost submission response may require waiting for the server time budget
|
||||
and starting a new run. A run ID is not an idempotency key.
|
||||
|
||||
The caller can recover with:
|
||||
|
||||
```sh
|
||||
python3 client/rapp_playwright.py status --handle .local/run-01/run.json
|
||||
python3 client/rapp_playwright.py cancel --handle .local/run-01/run.json
|
||||
python3 client/rapp_playwright.py download --handle .local/run-01/run.json
|
||||
```
|
||||
|
||||
For a nondefault endpoint, repeat `--url http://127.0.0.1:PORT` before each
|
||||
subcommand (or set `RAPP_PLAYWRIGHT_URL`). A run handle does not select the
|
||||
originating service. `invocation.json` retains the original service URL.
|
||||
|
||||
Disconnecting does not implicitly cancel an accepted job. Ctrl-C in the client
|
||||
requests cancellation; server deadlines still apply when the client disappears.
|
||||
|
||||
## Limits and evidence
|
||||
|
||||
Defaults: one active browser, four queued jobs, 64 retained runs, 60-second run
|
||||
budget (maximum 120 seconds), 40 steps, four viewports, width ≤1920 and height
|
||||
≤1600, 32 KiB submission, 20 MiB artifacts per run and 200 MiB retained artifact
|
||||
admission budget. Runs expire one hour after acceptance, including capabilities,
|
||||
receipts and server artifacts. The janitor runs every second. The managed pod
|
||||
adds CPU, memory and ephemeral-storage limits; PVC exhaustion remains an operator
|
||||
failure, not UI failure. Retained client copies have a consumer-owned lifecycle.
|
||||
|
||||
Receipt fields include ID, status/error, label, target, viewports, acceptance,
|
||||
start/finish/expiry timestamps, service/Playwright/browser versions, release and
|
||||
execution location, completed steps, per-step outcomes and artifact names/sizes.
|
||||
The Python client adds download SHA-256 hashes and saves the exact command in
|
||||
`invocation.json`. The service deliberately does not retain the full request.
|
||||
|
||||
Terminal result examples (selected fields; complete executed receipts are in
|
||||
[independent consumer evidence](evidence/2026-10-13-independent-local-consumer.md)):
|
||||
|
||||
```json
|
||||
{"status":"passed","error":null,"completed_steps":14}
|
||||
{"status":"failed","error":"ui_assertion_failed","steps":[{"viewport":"desktop","index":1,"action":"text","status":"failed"}]}
|
||||
{"status":"failed","error":"target_unreachable","steps":[],"artifacts":[]}
|
||||
```
|
||||
|
||||
An unavailable browser is rejected before admission with HTTP 503 and
|
||||
`{"error":"browser_unavailable"}`; a request with the wrong API version receives
|
||||
HTTP 409. A missing service/tunnel fails at the client as `service_unreachable`.
|
||||
|
||||
| Error | Meaning / next action |
|
||||
| --- | --- |
|
||||
| `browser_unavailable` / `browser_launch_failed` | Provider dependencies or browser failed; check preflight/operator logs |
|
||||
| `target_not_allowed` | Provider origin policy denied navigation or a redirect; request a scoped target change |
|
||||
| `target_unreachable` | DNS, connection, TLS or target HTTP error; verify address from the browser network |
|
||||
| `ui_assertion_failed` | Rendered UI/action did not satisfy the scenario; inspect step and available artifacts |
|
||||
| `run_timeout` | Whole run exceeded its budget; simplify or increase up to the documented maximum |
|
||||
| `browser_crashed` | Worker exited without a terminal result; provider runtime diagnosis |
|
||||
| `queue_full` / `artifact_capacity` | HTTP 429; retry after capacity frees |
|
||||
| `service_restarted` / `service_stopped` | Provider interruption; submit a fresh run |
|
||||
|
||||
For `trial` and `run`, exit codes are 0 passed, 2 failed UI assertion, 3 setup/
|
||||
infrastructure or other unsuccessful run, and 4 keyboard interruption. `status`,
|
||||
`cancel` and `download` return 0 when that API operation succeeds, regardless of
|
||||
the run outcome; inspect the receipt's `status` and `error`. A receipt cannot substitute
|
||||
for content-explorer accepting its own screenshot/keyboard task.
|
||||
|
||||
## Target reachability
|
||||
|
||||
A remote browser's `localhost` is the provider, not the caller's workstation.
|
||||
The origin gate covers redirects and subresources via a per-run HTTP/CONNECT
|
||||
proxy. DNS resolves once per connection; non-public addresses are denied unless
|
||||
the exact origin is explicitly listed in `PRIVATE_ORIGINS`. WebSockets and
|
||||
service workers are blocked. A consumer cannot request broader origin grants.
|
||||
|
||||
For a development app, first publish a reviewed private preview reachable from
|
||||
the service, or arrange an owner-approved reverse tunnel to a **dedicated target
|
||||
port** and register that exact origin. The browser API port-forward does not
|
||||
forward the consumer app. For local Docker development, explicitly allow the
|
||||
host gateway origin and bind the dev server to an address the container can
|
||||
reach. Never map all private networks just to make localhost work.
|
||||
|
||||
## Decision and upstream reuse
|
||||
|
||||
Chosen: bounded HTTP jobs, Python standard-library client, native Playwright
|
||||
inside a per-run worker, fresh Chromium process and per-viewport context. Native
|
||||
remote WebSocket sessions would require matching clients and expose broader
|
||||
automation/network access; defer them until a consumer needs arbitrary suites.
|
||||
This release adds the missing orchestration/evidence path, not a new browser engine.
|
||||
|
||||
The design reuses Playwright headless/context/screenshot patterns inspected in
|
||||
whynot-design, coulomb-social, core-hub and frontend-patterns. Official references:
|
||||
[Docker and version matching](https://playwright.dev/docs/docker),
|
||||
[browser contexts](https://playwright.dev/docs/api/class-browsercontext),
|
||||
[browser lifecycle](https://playwright.dev/docs/api/class-browser).
|
||||
The pinned package and browser image are both Playwright 1.64.0. The container
|
||||
uses a non-root user. Chromium's OS sandbox is configurable and defaults off for
|
||||
the trusted-target managed pilot; this is **not a hostile-page or mutually
|
||||
untrusted-tenant execution service**. No arbitrary user scripts run in Node.
|
||||
67
docs/content-explorer-pilot.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Content-explorer pilot
|
||||
|
||||
Consumer requirement: `CONTENT-WP-0000-T04`, responsive screenshots and a keyboard
|
||||
walkthrough. Baseline SHA: `b594d7433bf4e408b44215e25e5304696d6f840e`, as recorded
|
||||
in content-explorer's `research/baseline/ONCO_PIN`.
|
||||
|
||||
The first gate is the provider fixture trial: two viewports, Tab/Enter, observed
|
||||
focus and counter update, downloaded PNGs and a versioned receipt. It checks the
|
||||
service independently of OnCo. It does not complete the consumer task.
|
||||
|
||||
The second gate uses a runnable instance of the **pinned** OnCo checkout. Start
|
||||
its UI without invoking the corpus/network publication pipeline; document the
|
||||
exact launch path and ensure the provider can reach the target. Use its pinned
|
||||
page markup to write selectors and keyboard assertions. The consumer retains
|
||||
the upstream license/data boundary and decides which captures it may retain.
|
||||
|
||||
Candidate routes from the consumer baseline are `/`, `/cancers/`,
|
||||
`/cancers/head-and-neck/`, `/query/` and `/graph/`. Capture desktop and narrow
|
||||
screens, verify at least one meaningful rendered element, and document a real
|
||||
keyboard sequence and its observed result. Keep the pinned-source receipt
|
||||
separate from any exploratory run against `https://onco.cc/`, whose revision can
|
||||
change. A live website receipt must not be described as proof of the pinned SHA.
|
||||
|
||||
Consumer acceptance checklist:
|
||||
|
||||
- Run URL and pinned source SHA, target launch command, browser/service versions.
|
||||
- Commands, timestamps, run ID, downloaded artifacts and hashes.
|
||||
- Viewport dimensions and named page/route for each capture.
|
||||
- Keyboard steps with focus and action assertions; any failed step explained.
|
||||
- Separate infrastructure and semantic/visual mismatches.
|
||||
- Consumer review and task disposition in content-explorer, without automatic
|
||||
closure by the provider.
|
||||
|
||||
## Executed home-page pilot — 2026-10-13
|
||||
|
||||
Passed run `1e89ce26-fc3c-4eb3-8280-b36800b92efc`, using the pinned SHA above,
|
||||
Playwright 1.64.0 / Chromium 156.0.8078.4 in the local Docker provider. The
|
||||
original consumer checkout was left untouched. A temporary copy of its existing
|
||||
checkout and installed dependencies ran with:
|
||||
|
||||
```sh
|
||||
NODE_OPTIONS=--max-old-space-size=6144 /tmp/rapp-playwright-onco/node_modules/.bin/next dev --hostname 0.0.0.0 --port 3300
|
||||
python3 client/rapp_playwright.py run --spec examples/content-explorer-onco.json --out .local/content-explorer-onco-01
|
||||
```
|
||||
|
||||
The launch command ran from `/tmp/rapp-playwright-onco`; it did not invoke the
|
||||
corpus generation/publication scripts. The provider explicitly allowed
|
||||
`http://host.docker.internal:3300` as a private target and used Docker's host
|
||||
gateway mapping. This is local connectivity evidence, not a Railiance tunnel.
|
||||
|
||||
All 16 steps passed across 1280×800 and 390×844: the home heading matched,
|
||||
baseline screenshots were captured, Tab focused `a.skip-link`, Enter navigated
|
||||
to `/#main`, and post-keyboard screenshots were captured. Six PNG downloads
|
||||
were hashed; duplicate final/keyboard images share hashes. The URL assertion
|
||||
proves navigation to the anchor, not a complete accessibility audit.
|
||||
|
||||
Retained [receipt](evidence/2026-10-13-content-explorer-pilot/receipt.json),
|
||||
[preflight](evidence/2026-10-13-content-explorer-pilot/preflight.json),
|
||||
[invocation](evidence/2026-10-13-content-explorer-pilot/invocation.json),
|
||||
[desktop baseline](evidence/2026-10-13-content-explorer-pilot/desktop-2-baseline.png),
|
||||
[narrow baseline](evidence/2026-10-13-content-explorer-pilot/narrow-2-baseline.png),
|
||||
[desktop result](evidence/2026-10-13-content-explorer-pilot/desktop-final.png) and
|
||||
[narrow result](evidence/2026-10-13-content-explorer-pilot/narrow-final.png) exclude
|
||||
the secret client run handle. These are home-page smoke results. Other routes,
|
||||
search, graph interaction and consumer visual acceptance remain unverified.
|
||||
The handoff to content-explorer is message
|
||||
`621635c9-0a72-4a88-9fd6-2ee1fbddc68f`; its T04 remains consumer-owned.
|
||||
13
docs/evidence/2026-10-13-browser-integration.log
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
docker run --rm --init --user 1000:1000 --read-only --tmpfs /tmp:rw,size=512m --shm-size=256m --memory=1g --cpus=1 --cap-drop ALL --security-opt no-new-privileges --mount type=bind,src=/home/worsch/rapp-playwright,dst=/app,readonly -w /app mcr.microsoft.com/playwright:v1.64.0-noble@sha256:06a9939e57531807f8d5fd76ce44b53165ffb7d7501d87ab10e285c20b1e971f node --test tests/integration.mjs
|
||||
✔ real browser produces responsive keyboard evidence, fresh state and scoped artifacts (34881.527994ms)
|
||||
✔ UI failures, unreachable targets and forbidden redirects remain distinguishable (33199.838535ms)
|
||||
✔ queue limits, cancellation, timeout, worker crash and subsequent recovery (24287.921891ms)
|
||||
✔ restart marks abandoned runs interrupted and expiry removes artifacts (6352.642442ms)
|
||||
ℹ tests 4
|
||||
ℹ suites 0
|
||||
ℹ pass 4
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 99315.842719
|
||||
86
docs/evidence/2026-10-13-build.log
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
npm run check
|
||||
|
||||
> rapp-playwright@0.1.0 check
|
||||
> node --check src/server.mjs && node --check src/worker.mjs && node --check src/proxy.mjs
|
||||
|
||||
python3 -m py_compile client/rapp_playwright.py tools/render.py
|
||||
npm test
|
||||
|
||||
> rapp-playwright@0.1.0 test
|
||||
> node --test tests/*.test.mjs
|
||||
|
||||
✔ validated contract requires explicit protocol, allowed origin, bounded input and assertions (3.01824ms)
|
||||
✔ network gate denies non-public addresses unless explicitly granted (6.721745ms)
|
||||
✔ proxy pins DNS and denies unlisted origins and private addresses before connection (49.97023ms)
|
||||
ℹ tests 3
|
||||
ℹ suites 0
|
||||
ℹ pass 3
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 196.640985
|
||||
docker build -t forgejo.coulomb.social/coulomb/rapp-playwright:0.1.0 .
|
||||
#0 building with "default" instance using docker driver
|
||||
|
||||
#1 [internal] load build definition from Dockerfile
|
||||
#1 transferring dockerfile: 773B done
|
||||
#1 DONE 0.0s
|
||||
|
||||
#2 [internal] load metadata for mcr.microsoft.com/playwright:v1.64.0-noble@sha256:06a9939e57531807f8d5fd76ce44b53165ffb7d7501d87ab10e285c20b1e971f
|
||||
#2 DONE 0.1s
|
||||
|
||||
#3 [internal] load .dockerignore
|
||||
#3 transferring context: 109B done
|
||||
#3 DONE 0.0s
|
||||
|
||||
#4 [internal] load build context
|
||||
#4 DONE 0.0s
|
||||
|
||||
#5 [ 1/10] FROM mcr.microsoft.com/playwright:v1.64.0-noble@sha256:06a9939e57531807f8d5fd76ce44b53165ffb7d7501d87ab10e285c20b1e971f
|
||||
#5 resolve mcr.microsoft.com/playwright:v1.64.0-noble@sha256:06a9939e57531807f8d5fd76ce44b53165ffb7d7501d87ab10e285c20b1e971f 0.1s done
|
||||
#5 DONE 0.1s
|
||||
|
||||
#4 [internal] load build context
|
||||
#4 transferring context: 18.23kB 0.0s done
|
||||
#4 DONE 0.0s
|
||||
|
||||
#6 [ 3/10] RUN apt-get update && apt-get install -y --no-install-recommends tini=0.19.0-1 && rm -rf /var/lib/apt/lists/*
|
||||
#6 CACHED
|
||||
|
||||
#7 [ 4/10] COPY package.json package-lock.json ./
|
||||
#7 CACHED
|
||||
|
||||
#8 [ 2/10] WORKDIR /app
|
||||
#8 CACHED
|
||||
|
||||
#9 [ 5/10] RUN npm ci --omit=dev --ignore-scripts && npm cache clean --force
|
||||
#9 CACHED
|
||||
|
||||
#10 [ 6/10] COPY src ./src
|
||||
#10 DONE 0.1s
|
||||
|
||||
#11 [ 7/10] COPY fixtures ./fixtures
|
||||
#11 DONE 0.1s
|
||||
|
||||
#12 [ 8/10] COPY client ./client
|
||||
#12 DONE 0.1s
|
||||
|
||||
#13 [ 9/10] COPY --chmod=0555 tools/chromium-launcher.py ./tools/chromium-launcher.py
|
||||
#13 DONE 0.1s
|
||||
|
||||
#14 [10/10] RUN mkdir /data && chown pwuser:pwuser /data
|
||||
#14 DONE 0.5s
|
||||
|
||||
#15 exporting to image
|
||||
#15 exporting layers
|
||||
#15 exporting layers 0.5s done
|
||||
#15 exporting manifest sha256:20d8310d44cd807579aca06df01090a7bb27892ce387519f9957e4dd67f64cc7 0.0s done
|
||||
#15 exporting config sha256:5bda1139fda091648b1e984f6be8119291ad5616263b206d62ac7986ba2762b3 0.0s done
|
||||
#15 exporting attestation manifest sha256:3267896f3b778dedce0b2c92ea1c67b64a6e168417aa422c4750dcbcb0d165f5 0.1s done
|
||||
#15 exporting manifest list sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441
|
||||
#15 exporting manifest list sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441 0.0s done
|
||||
#15 naming to forgejo.coulomb.social/coulomb/rapp-playwright:0.1.0 done
|
||||
#15 unpacking to forgejo.coulomb.social/coulomb/rapp-playwright:0.1.0
|
||||
#15 unpacking to forgejo.coulomb.social/coulomb/rapp-playwright:0.1.0 0.2s done
|
||||
#15 DONE 1.0s
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "621635c9-0a72-4a88-9fd6-2ee1fbddc68f",
|
||||
"from_agent": "rapp-playwright",
|
||||
"to_agent": "content-explorer",
|
||||
"subject": "Pinned OnCo responsive and keyboard pilot evidence available",
|
||||
"body": "WP-0004-T02 provider pilot passed on pinned OnCo SHA b594d7433bf4e408b44215e25e5304696d6f840e. Original upstream-onco checkout untouched; temporary copy served with Next dev on port3300. examples/content-explorer-onco.json asserted home heading, captured 1280x800 / 390x844, Tab focus on a.skip-link, Enter and #main URL. Run 1e89ce26-fc3c-4eb3-8280-b36800b92efc passed all16steps and6PNGs. Sanitized receipts and selected PNGs: /home/worsch/rapp-playwright/docs/evidence/2026-10-13-content-explorer-pilot/. Full local outputs .local/content-explorer-onco-01 (private run handle must not be published). Please review for CONTENT-WP-0000-T04; you own acceptance and any further route/keyboard coverage. This is workstation-container evidence, not managed Railiance or current live onco.cc. Provider will retain exact launch command and limits in docs/content-explorer-pilot.md.",
|
||||
"thread_id": null,
|
||||
"read_at": null,
|
||||
"archived_at": null,
|
||||
"created_at": "2026-10-13T17:12:27.179074Z",
|
||||
"kind": "message",
|
||||
"expires_at": null,
|
||||
"supersedes_id": null,
|
||||
"reader": null,
|
||||
"delivered_at": null,
|
||||
"acknowledged_at": null
|
||||
}
|
||||
|
After Width: | Height: | Size: 335 KiB |
|
After Width: | Height: | Size: 307 KiB |
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"argv": [
|
||||
"client/rapp_playwright.py",
|
||||
"run",
|
||||
"--spec",
|
||||
"examples/content-explorer-onco.json",
|
||||
"--out",
|
||||
".local/content-explorer-onco-01"
|
||||
],
|
||||
"service_url": "http://127.0.0.1:18081"
|
||||
}
|
||||
|
After Width: | Height: | Size: 138 KiB |
BIN
docs/evidence/2026-10-13-content-explorer-pilot/narrow-final.png
Normal file
|
After Width: | Height: | Size: 127 KiB |
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"available": true,
|
||||
"error": null,
|
||||
"checked_at": "2026-10-13T16:43:51.651Z",
|
||||
"browser_version": "156.0.8078.4",
|
||||
"api_version": "1",
|
||||
"service_version": "0.1.0",
|
||||
"playwright_version": "1.64.0",
|
||||
"execution_location": "workstation-container",
|
||||
"release": "development",
|
||||
"allowed_origins": [
|
||||
"http://host.docker.internal:3300",
|
||||
"https://onco.cc"
|
||||
],
|
||||
"fixture": true,
|
||||
"active": 0,
|
||||
"queued": 0,
|
||||
"limits": {
|
||||
"concurrency": 1,
|
||||
"max_queue": 4,
|
||||
"max_timeout_ms": 120000,
|
||||
"retention_ms": 3600000,
|
||||
"max_runs": 64
|
||||
}
|
||||
}
|
||||
214
docs/evidence/2026-10-13-content-explorer-pilot/receipt.json
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
{
|
||||
"id": "1e89ce26-fc3c-4eb3-8280-b36800b92efc",
|
||||
"api_version": "1",
|
||||
"status": "passed",
|
||||
"error": null,
|
||||
"label": "content-explorer-pinned-onco-home",
|
||||
"target": "http://host.docker.internal:3300/",
|
||||
"viewports": [
|
||||
{
|
||||
"name": "desktop",
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
},
|
||||
{
|
||||
"name": "narrow",
|
||||
"width": 390,
|
||||
"height": 844
|
||||
}
|
||||
],
|
||||
"timeout_ms": 120000,
|
||||
"created_at": "2026-10-13T16:43:53.447Z",
|
||||
"expires_at": "2026-10-13T17:43:53.447Z",
|
||||
"service_version": "0.1.0",
|
||||
"playwright_version": "1.64.0",
|
||||
"release": "development",
|
||||
"execution_location": "workstation-container",
|
||||
"browser_version": "156.0.8078.4",
|
||||
"completed_steps": 16,
|
||||
"steps": [
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 0,
|
||||
"action": "visible",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:44:10.155Z",
|
||||
"selector": "h1"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 1,
|
||||
"action": "text",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:44:14.051Z",
|
||||
"selector": "h1"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 2,
|
||||
"action": "screenshot",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:44:14.353Z"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 3,
|
||||
"action": "press",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:44:17.012Z",
|
||||
"key": "Tab"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 4,
|
||||
"action": "focused",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:44:17.073Z",
|
||||
"selector": "a.skip-link"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 5,
|
||||
"action": "press",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:44:17.255Z",
|
||||
"key": "Enter"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 6,
|
||||
"action": "url",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:44:17.296Z"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 7,
|
||||
"action": "screenshot",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:44:17.296Z"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 0,
|
||||
"action": "visible",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:44:30.719Z",
|
||||
"selector": "h1"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 1,
|
||||
"action": "text",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:44:31.430Z",
|
||||
"selector": "h1"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 2,
|
||||
"action": "screenshot",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:44:31.588Z"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 3,
|
||||
"action": "press",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:44:34.905Z",
|
||||
"key": "Tab"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 4,
|
||||
"action": "focused",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:44:34.968Z",
|
||||
"selector": "a.skip-link"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 5,
|
||||
"action": "press",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:44:35.026Z",
|
||||
"key": "Enter"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 6,
|
||||
"action": "url",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:44:35.085Z"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 7,
|
||||
"action": "screenshot",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:44:35.085Z"
|
||||
}
|
||||
],
|
||||
"artifacts": [
|
||||
{
|
||||
"name": "desktop-2-baseline.png",
|
||||
"bytes": 342951
|
||||
},
|
||||
{
|
||||
"name": "desktop-7-keyboard-skip.png",
|
||||
"bytes": 314186
|
||||
},
|
||||
{
|
||||
"name": "desktop-final.png",
|
||||
"bytes": 314186
|
||||
},
|
||||
{
|
||||
"name": "narrow-2-baseline.png",
|
||||
"bytes": 140972
|
||||
},
|
||||
{
|
||||
"name": "narrow-7-keyboard-skip.png",
|
||||
"bytes": 129764
|
||||
},
|
||||
{
|
||||
"name": "narrow-final.png",
|
||||
"bytes": 129764
|
||||
}
|
||||
],
|
||||
"started_at": "2026-10-13T16:43:53.457Z",
|
||||
"blocked_requests": 0,
|
||||
"finished_at": "2026-10-13T16:44:36.275Z",
|
||||
"downloads": [
|
||||
{
|
||||
"name": "desktop-2-baseline.png",
|
||||
"bytes": 342951,
|
||||
"sha256": "c36fb610e09bf04ba76e9755103ab2c36d39bfcfc286be8590c517c9fa7705b8"
|
||||
},
|
||||
{
|
||||
"name": "desktop-7-keyboard-skip.png",
|
||||
"bytes": 314186,
|
||||
"sha256": "a64096394d8750ef422b1f5b6b0f939607cb13a57703f5b2d39c2be84bf2e3c7"
|
||||
},
|
||||
{
|
||||
"name": "desktop-final.png",
|
||||
"bytes": 314186,
|
||||
"sha256": "a64096394d8750ef422b1f5b6b0f939607cb13a57703f5b2d39c2be84bf2e3c7"
|
||||
},
|
||||
{
|
||||
"name": "narrow-2-baseline.png",
|
||||
"bytes": 140972,
|
||||
"sha256": "7c3a5278d3dcd35413658fbfceb6d19e176439f2ffc2e628f7bbfea6ee6b641f"
|
||||
},
|
||||
{
|
||||
"name": "narrow-7-keyboard-skip.png",
|
||||
"bytes": 129764,
|
||||
"sha256": "865fe283ca6a38742ff176c67f4eeee68f37ac92d8d60a6e67fb8b00778ba58e"
|
||||
},
|
||||
{
|
||||
"name": "narrow-final.png",
|
||||
"bytes": 129764,
|
||||
"sha256": "865fe283ca6a38742ff176c67f4eeee68f37ac92d8d60a6e67fb8b00778ba58e"
|
||||
}
|
||||
]
|
||||
}
|
||||
18
docs/evidence/2026-10-13-contract-decision.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"workplan_id": "98fb515e-9239-57d5-9ffa-8a5c5a86ed90",
|
||||
"id": "90c6d03a-6308-4741-ab71-ac837e242e85",
|
||||
"topic_id": "595afc64-bd28-47bf-aafb-ba230b28371b",
|
||||
"title": "Use bounded browser jobs and assign platform lifecycle ownership",
|
||||
"description": "Implement protocol-v1 HTTP jobs with Python stdlib client, pinned Playwright 1.64.0 / Chromium image, per-run process/context, private RBAC port-forward and scoped run capabilities. Founder explicitly selected railiance-platform as lifecycle owner on 2026-10-13; service implementation remains in rapp-playwright. Managed activation still requires platform admission.",
|
||||
"decision_type": "made",
|
||||
"status": "resolved",
|
||||
"rationale": "Independent client avoids matching local browser installs; bounded jobs constrain network and resource use. See docs/consumer-contract.md. Ownership follows the founder response, not inferred owner approval.",
|
||||
"decided_by": "codex (implementation contract); founder (lifecycle owner selection)",
|
||||
"decided_at": null,
|
||||
"deadline": null,
|
||||
"escalation_note": null,
|
||||
"superseded_by": null,
|
||||
"created_at": "2026-10-13T17:15:54.752167Z",
|
||||
"updated_at": "2026-10-13T17:15:54.752167Z",
|
||||
"workstream_id": "98fb515e-9239-57d5-9ffa-8a5c5a86ed90"
|
||||
}
|
||||
39
docs/evidence/2026-10-13-discovery-readback.json
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"url": "https://reuse.coulomb.social/v1/federated",
|
||||
"matches": [
|
||||
{
|
||||
"id": "capability.infotech.headless-browser",
|
||||
"name": "Headless Browser Automation",
|
||||
"summary": "Headless browser automation with Playwright and Chromium for UI testing, screenshots and page extraction; recommended default based on existing whynot-design, coulomb-social, core-hub and frontend-patterns implementations. A rapp-playwright-provided service will be available shortly (announced 2026-10-13; planned, not yet verified available).",
|
||||
"vector": "D3 / A2 / C2 / R2",
|
||||
"domain": "infotech",
|
||||
"status": "reviewed",
|
||||
"owner": "reuse-surface",
|
||||
"path": "registry/capabilities/capability.infotech.headless-browser.md",
|
||||
"tags": [
|
||||
"headless",
|
||||
"browser",
|
||||
"automation",
|
||||
"playwright",
|
||||
"chromium",
|
||||
"testing",
|
||||
"screenshots",
|
||||
"extraction",
|
||||
"firefox",
|
||||
"webkit",
|
||||
"selenium",
|
||||
"puppeteer"
|
||||
],
|
||||
"consumption_modes": [
|
||||
"informational",
|
||||
"source adaptation",
|
||||
"JavaScript library",
|
||||
"Python library"
|
||||
],
|
||||
"source_repo": "reuse-surface",
|
||||
"source_url": "https://forgejo.coulomb.social/coulomb/reuse-surface/raw/branch/main/registry/indexes/capabilities.yaml",
|
||||
"source_index": "/data/cache/reuse-surface.yaml"
|
||||
}
|
||||
],
|
||||
"composed_at": "2026-10-13T14:03:25+00:00"
|
||||
}
|
||||
49
docs/evidence/2026-10-13-final-image-restart.json
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"action": "docker restart rapp-playwright-final",
|
||||
"preflight": {
|
||||
"available": true,
|
||||
"error": null,
|
||||
"checked_at": "2026-10-13T17:17:36.100Z",
|
||||
"browser_version": "156.0.8078.4",
|
||||
"api_version": "1",
|
||||
"service_version": "0.1.0",
|
||||
"playwright_version": "1.64.0",
|
||||
"execution_location": "workstation-release-check",
|
||||
"release": "sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441",
|
||||
"allowed_origins": [],
|
||||
"fixture": true,
|
||||
"active": 0,
|
||||
"queued": 0,
|
||||
"limits": {
|
||||
"concurrency": 1,
|
||||
"max_queue": 4,
|
||||
"max_timeout_ms": 120000,
|
||||
"retention_ms": 3600000,
|
||||
"max_runs": 64
|
||||
}
|
||||
},
|
||||
"retrieved_run_id": "35124424-950a-4339-adea-45525b470cab",
|
||||
"status": "passed",
|
||||
"artifacts": [
|
||||
{
|
||||
"name": "desktop-6-keyboard-result.png",
|
||||
"bytes": 26622,
|
||||
"sha256": "b15998824256b86c74f2bbb75f061061be65db0b4f3bb5444529ac69a723cdad"
|
||||
},
|
||||
{
|
||||
"name": "desktop-final.png",
|
||||
"bytes": 26622,
|
||||
"sha256": "b15998824256b86c74f2bbb75f061061be65db0b4f3bb5444529ac69a723cdad"
|
||||
},
|
||||
{
|
||||
"name": "narrow-6-keyboard-result.png",
|
||||
"bytes": 24280,
|
||||
"sha256": "49d27cb169b8e180ce93969c6d2b8619a7684cf035bb9dcae9cd91d8bd9952e8"
|
||||
},
|
||||
{
|
||||
"name": "narrow-final.png",
|
||||
"bytes": 24280,
|
||||
"sha256": "49d27cb169b8e180ce93969c6d2b8619a7684cf035bb9dcae9cd91d8bd9952e8"
|
||||
}
|
||||
]
|
||||
}
|
||||
40
docs/evidence/2026-10-13-implementation-checks.json
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"observed_at": "2026-10-13T17:20:07.614706+00:00",
|
||||
"service_version": "0.1.0",
|
||||
"playwright_version": "1.64.0",
|
||||
"browser_version": "156.0.8078.4",
|
||||
"local_image": "forgejo.coulomb.social/coulomb/rapp-playwright@sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441",
|
||||
"registry_publication": "blocked: blob PUT HTTP 499 and 500; remote manifest inspect returns manifest unknown",
|
||||
"unit_tests": {
|
||||
"passed": 3,
|
||||
"command": "make test"
|
||||
},
|
||||
"browser_integration": {
|
||||
"passed": 4,
|
||||
"command": "make test-integration",
|
||||
"log": "2026-10-13-browser-integration.log"
|
||||
},
|
||||
"additional_browser_test": {
|
||||
"passed": 1,
|
||||
"name": "missing browser fails preflight and prevents admission",
|
||||
"log": "2026-10-13-missing-browser.log"
|
||||
},
|
||||
"family_declaration_validation": "2 declaration(s) ok with rail-kubernetes dependency",
|
||||
"render": "passed with exact candidate digest; floating references rejected",
|
||||
"kubernetes_client_dry_run": "7 objects validated; no objects persisted",
|
||||
"kubernetes_server_dry_run": "not run: namespace absent and admission pending",
|
||||
"cluster_mutations": "none",
|
||||
"capacity": {
|
||||
"node_ip": "92.205.62.239",
|
||||
"node_ready": true,
|
||||
"cpu_request_m": 3460,
|
||||
"cpu_allocatable_m": 4000,
|
||||
"memory_requests_mi": 9472
|
||||
},
|
||||
"final_container": "rapp-playwright-final",
|
||||
"local_url": "http://127.0.0.1:18083",
|
||||
"independent_final_run": "35124424-950a-4339-adea-45525b470cab",
|
||||
"restart": "completed receipt plus four hashed PNGs retrieved after docker restart",
|
||||
"platform_request": "e77247e5-ce25-4711-a64f-af81a9f7cbb1",
|
||||
"managed_status": "not deployed; namespace playwright absent"
|
||||
}
|
||||
67
docs/evidence/2026-10-13-implementation.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Implementation verification — 2026-10-13
|
||||
|
||||
The bounded service, Python client and managed package are implemented. Local
|
||||
runtime and independent consumption passed. Image publication and managed
|
||||
activation are blocked by external delivery dependencies; neither is claimed.
|
||||
|
||||
[Machine-readable checks](2026-10-13-implementation-checks.json) record the
|
||||
candidate image identity, commands, resource observations and handoff IDs.
|
||||
|
||||
- `make test`: syntax/compile checks and 3 unit tests passed.
|
||||
- `make test-integration`: 4 real-browser cases passed in 99 seconds under
|
||||
non-root, read-only Docker, dropped capabilities, no-new-privileges, 1 CPU,
|
||||
1Gi memory and bounded tmpfs. [Log](2026-10-13-browser-integration.log).
|
||||
- The added missing-browser case separately passed: HTTP 503 launch preflight
|
||||
and rejected run admission. [Log](2026-10-13-missing-browser.log).
|
||||
- [Build log](2026-10-13-build.log): final pinned runtime image built successfully.
|
||||
- [Independent trials](2026-10-13-independent-local-consumer.md): initial and
|
||||
final-image consumer trials, keyboard/screenshots and failure cases passed.
|
||||
- [Final-image restart](2026-10-13-final-image-restart.json): availability recovered
|
||||
and completed run/artifacts remained retrievable from its retained volume.
|
||||
- [Pinned OnCo pilot](../content-explorer-pilot.md): home-page responsive and
|
||||
skip-link evidence passed; content-explorer retains its own acceptance.
|
||||
|
||||
The integration suite checks fresh cookie/localStorage state, foreign-token
|
||||
receipt/artifact denial, protocol mismatch, assertion failure, unavailable target,
|
||||
blocked redirect without request leakage, concurrent admission bounds, queued
|
||||
and active cancellation, hard timeout, forced worker crash with no remaining
|
||||
browser process group, subsequent recovery, interrupted persisted work and
|
||||
expiry deletion. Temporary browser profiles and home directories are scoped to
|
||||
the supervised worker and removed after its exit. This is not proof of safety
|
||||
for hostile pages or mutually untrusted tenants; Chromium's OS sandbox is off.
|
||||
|
||||
Package schema validation passed against railiance-master with rail-kubernetes
|
||||
references. Digest rendering and Kubernetes client validation passed for all
|
||||
seven objects. No server-side admission success is claimed. The namespace does
|
||||
not exist and no cluster object was created in this session. At the recorded
|
||||
capacity check, railiance01 was Ready with 3460m CPU requested out of 4000m;
|
||||
the package requests 100m and uses Recreate. Recheck before deployment.
|
||||
|
||||
## Publication and owner dependencies
|
||||
|
||||
Three OCI upload attempts encountered final blob PUT failures (HTTP 499, then
|
||||
HTTP 500); smaller layers uploaded. Remote manifest inspection returned
|
||||
`manifest unknown`. Local image index:
|
||||
`sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441`.
|
||||
This digest identifies a built image, not an available registry release.
|
||||
|
||||
The founder selected railiance-platform lifecycle ownership. Admission request
|
||||
`c82d75f3-3191-410d-a8c8-fd647bf7f3ef` and the final
|
||||
[platform handoff](2026-10-13-platform-implementation-notification.json) await
|
||||
an owner response. Cluster access binding, live restart and rollback remain
|
||||
WP-0004-T01 work. There is no previous managed release for cross-version rollback.
|
||||
|
||||
The [reuse-surface handoff](2026-10-13-reuse-surface-implementation-notification.json)
|
||||
asks the owner to publish verified local provider references without claiming
|
||||
managed availability. Readback at 19:18 Europe/Berlin still showed the source-only
|
||||
D3/A2/C2/R2 entry and planned provider; no maintained update was inferred from
|
||||
message delivery. The [published discovery readback](2026-10-13-discovery-readback.json)
|
||||
also returned the planned provider and D3/A2/C2/R2 from the live federated API.
|
||||
WP-0004-T03 remains open. The
|
||||
[content-explorer handoff](2026-10-13-content-explorer-implementation-notification.json)
|
||||
delivers the home-page evidence without closing CONTENT-WP-0000-T04.
|
||||
|
||||
The development provider, failed intermediate check container and temporary
|
||||
OnCo server were stopped. The final local provider remains on loopback port
|
||||
18083 for inspection; it is a workstation process, not a managed endpoint.
|
||||
Server artifacts expire after one hour; the linked safe evidence is retained.
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
[
|
||||
{
|
||||
"name": "desktop-6-keyboard-result.png",
|
||||
"dimensions": [
|
||||
1280,
|
||||
800
|
||||
],
|
||||
"sha256": "b15998824256b86c74f2bbb75f061061be65db0b4f3bb5444529ac69a723cdad",
|
||||
"bytes": 26622
|
||||
},
|
||||
{
|
||||
"name": "desktop-final.png",
|
||||
"dimensions": [
|
||||
1280,
|
||||
800
|
||||
],
|
||||
"sha256": "b15998824256b86c74f2bbb75f061061be65db0b4f3bb5444529ac69a723cdad",
|
||||
"bytes": 26622
|
||||
},
|
||||
{
|
||||
"name": "narrow-6-keyboard-result.png",
|
||||
"dimensions": [
|
||||
390,
|
||||
844
|
||||
],
|
||||
"sha256": "49d27cb169b8e180ce93969c6d2b8619a7684cf035bb9dcae9cd91d8bd9952e8",
|
||||
"bytes": 24280
|
||||
},
|
||||
{
|
||||
"name": "narrow-final.png",
|
||||
"dimensions": [
|
||||
390,
|
||||
844
|
||||
],
|
||||
"sha256": "49d27cb169b8e180ce93969c6d2b8619a7684cf035bb9dcae9cd91d8bd9952e8",
|
||||
"bytes": 24280
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"trial_exit": 0,
|
||||
"intentional_ui_failure_exit": 2,
|
||||
"failed_ui_status_command_exit": 0,
|
||||
"unavailable_service_preflight": {
|
||||
"argv": [
|
||||
"python3",
|
||||
"client/rapp_playwright.py",
|
||||
"--url",
|
||||
"http://127.0.0.1:18089",
|
||||
"preflight"
|
||||
],
|
||||
"exit": 3,
|
||||
"response": {
|
||||
"error": "service_unreachable: establish the documented tunnel and retry"
|
||||
}
|
||||
},
|
||||
"trial_directory_mode": "0700",
|
||||
"trial_handle_mode": "0600",
|
||||
"external_target_tested": false,
|
||||
"status_without_url_override": {
|
||||
"exit": 3,
|
||||
"response": {
|
||||
"error": "HTTP 404: not_found"
|
||||
},
|
||||
"service_url": "http://127.0.0.1:18081"
|
||||
},
|
||||
"failed_ui_status_command_url": "http://127.0.0.1:18083"
|
||||
}
|
||||
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"api_version": "1",
|
||||
"target": "fixture",
|
||||
"label": "independent-final-ui-failure",
|
||||
"timeout_ms": 30000,
|
||||
"viewports": [
|
||||
{
|
||||
"name": "desktop",
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
}
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"action": "screenshot",
|
||||
"name": "before-failure"
|
||||
},
|
||||
{
|
||||
"action": "text",
|
||||
"selector": "#count",
|
||||
"value": "99999"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"argv": [
|
||||
"client/rapp_playwright.py",
|
||||
"--url",
|
||||
"http://127.0.0.1:18083",
|
||||
"trial",
|
||||
"--out",
|
||||
".local/independent-final-01"
|
||||
],
|
||||
"service_url": "http://127.0.0.1:18083"
|
||||
}
|
||||
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"available": true,
|
||||
"error": null,
|
||||
"checked_at": "2026-10-13T17:14:29.108Z",
|
||||
"browser_version": "156.0.8078.4",
|
||||
"api_version": "1",
|
||||
"service_version": "0.1.0",
|
||||
"playwright_version": "1.64.0",
|
||||
"execution_location": "workstation-release-check",
|
||||
"release": "sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441",
|
||||
"allowed_origins": [],
|
||||
"fixture": true,
|
||||
"active": 0,
|
||||
"queued": 0,
|
||||
"limits": {
|
||||
"concurrency": 1,
|
||||
"max_queue": 4,
|
||||
"max_timeout_ms": 120000,
|
||||
"retention_ms": 3600000,
|
||||
"max_runs": 64
|
||||
}
|
||||
}
|
||||
184
docs/evidence/2026-10-13-independent-final-consumer/receipt.json
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
{
|
||||
"id": "35124424-950a-4339-adea-45525b470cab",
|
||||
"api_version": "1",
|
||||
"status": "passed",
|
||||
"error": null,
|
||||
"label": "independent-browser-trial",
|
||||
"target": "fixture",
|
||||
"viewports": [
|
||||
{
|
||||
"name": "desktop",
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
},
|
||||
{
|
||||
"name": "narrow",
|
||||
"width": 390,
|
||||
"height": 844
|
||||
}
|
||||
],
|
||||
"timeout_ms": 60000,
|
||||
"created_at": "2026-10-13T17:14:50.050Z",
|
||||
"expires_at": "2026-10-13T18:14:50.050Z",
|
||||
"service_version": "0.1.0",
|
||||
"playwright_version": "1.64.0",
|
||||
"release": "sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441",
|
||||
"execution_location": "workstation-release-check",
|
||||
"browser_version": "156.0.8078.4",
|
||||
"completed_steps": 14,
|
||||
"steps": [
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 0,
|
||||
"action": "visible",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T17:14:56.554Z",
|
||||
"selector": "h1"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 1,
|
||||
"action": "text",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T17:14:57.033Z",
|
||||
"selector": "#count"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 2,
|
||||
"action": "press",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T17:14:57.233Z",
|
||||
"key": "Tab"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 3,
|
||||
"action": "focused",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T17:14:57.247Z",
|
||||
"selector": "#increment"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 4,
|
||||
"action": "press",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T17:14:57.341Z",
|
||||
"key": "Enter"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 5,
|
||||
"action": "text",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T17:14:57.354Z",
|
||||
"selector": "#count"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 6,
|
||||
"action": "screenshot",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T17:14:57.441Z"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 0,
|
||||
"action": "visible",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T17:15:00.135Z",
|
||||
"selector": "h1"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 1,
|
||||
"action": "text",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T17:15:00.349Z",
|
||||
"selector": "#count"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 2,
|
||||
"action": "press",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T17:15:00.547Z",
|
||||
"key": "Tab"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 3,
|
||||
"action": "focused",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T17:15:00.633Z",
|
||||
"selector": "#increment"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 4,
|
||||
"action": "press",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T17:15:00.644Z",
|
||||
"key": "Enter"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 5,
|
||||
"action": "text",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T17:15:00.654Z",
|
||||
"selector": "#count"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 6,
|
||||
"action": "screenshot",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T17:15:00.734Z"
|
||||
}
|
||||
],
|
||||
"artifacts": [
|
||||
{
|
||||
"name": "desktop-6-keyboard-result.png",
|
||||
"bytes": 26622
|
||||
},
|
||||
{
|
||||
"name": "desktop-final.png",
|
||||
"bytes": 26622
|
||||
},
|
||||
{
|
||||
"name": "narrow-6-keyboard-result.png",
|
||||
"bytes": 24280
|
||||
},
|
||||
{
|
||||
"name": "narrow-final.png",
|
||||
"bytes": 24280
|
||||
}
|
||||
],
|
||||
"started_at": "2026-10-13T17:14:50.055Z",
|
||||
"blocked_requests": 13,
|
||||
"finished_at": "2026-10-13T17:15:01.861Z",
|
||||
"downloads": [
|
||||
{
|
||||
"name": "desktop-6-keyboard-result.png",
|
||||
"bytes": 26622,
|
||||
"sha256": "b15998824256b86c74f2bbb75f061061be65db0b4f3bb5444529ac69a723cdad"
|
||||
},
|
||||
{
|
||||
"name": "desktop-final.png",
|
||||
"bytes": 26622,
|
||||
"sha256": "b15998824256b86c74f2bbb75f061061be65db0b4f3bb5444529ac69a723cdad"
|
||||
},
|
||||
{
|
||||
"name": "narrow-6-keyboard-result.png",
|
||||
"bytes": 24280,
|
||||
"sha256": "49d27cb169b8e180ce93969c6d2b8619a7684cf035bb9dcae9cd91d8bd9952e8"
|
||||
},
|
||||
{
|
||||
"name": "narrow-final.png",
|
||||
"bytes": 24280,
|
||||
"sha256": "49d27cb169b8e180ce93969c6d2b8619a7684cf035bb9dcae9cd91d8bd9952e8"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"argv": [
|
||||
"client/rapp_playwright.py",
|
||||
"--url",
|
||||
"http://127.0.0.1:18083",
|
||||
"run",
|
||||
"--spec",
|
||||
".local/independent-final-01/failure-spec.json",
|
||||
"--out",
|
||||
".local/independent-final-ui-failure-01"
|
||||
],
|
||||
"service_url": "http://127.0.0.1:18083"
|
||||
}
|
||||
|
After Width: | Height: | Size: 25 KiB |
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"id": "75c6b50e-94a1-467f-98fc-7e8937f4362e",
|
||||
"api_version": "1",
|
||||
"status": "failed",
|
||||
"error": "ui_assertion_failed",
|
||||
"label": "independent-final-ui-failure",
|
||||
"target": "fixture",
|
||||
"viewports": [
|
||||
{
|
||||
"name": "desktop",
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
}
|
||||
],
|
||||
"timeout_ms": 30000,
|
||||
"created_at": "2026-10-13T17:15:12.803Z",
|
||||
"expires_at": "2026-10-13T18:15:12.803Z",
|
||||
"service_version": "0.1.0",
|
||||
"playwright_version": "1.64.0",
|
||||
"release": "sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441",
|
||||
"execution_location": "workstation-release-check",
|
||||
"browser_version": "156.0.8078.4",
|
||||
"completed_steps": 1,
|
||||
"steps": [
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 0,
|
||||
"action": "screenshot",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T17:15:22.422Z"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 1,
|
||||
"action": "text",
|
||||
"status": "failed",
|
||||
"at": "2026-10-13T17:15:23.542Z",
|
||||
"selector": "#count"
|
||||
}
|
||||
],
|
||||
"artifacts": [
|
||||
{
|
||||
"name": "desktop-0-before-failure.png",
|
||||
"bytes": 25740
|
||||
}
|
||||
],
|
||||
"started_at": "2026-10-13T17:15:12.806Z",
|
||||
"blocked_requests": 9,
|
||||
"finished_at": "2026-10-13T17:15:29.015Z",
|
||||
"downloads": [
|
||||
{
|
||||
"name": "desktop-0-before-failure.png",
|
||||
"bytes": 25740,
|
||||
"sha256": "141dcadca4774da5fefec835bb789c5221685d3f453070a593e69aa6ad4782ce"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"http_status": 404,
|
||||
"body": {
|
||||
"error": "not_found"
|
||||
}
|
||||
}
|
||||
140
docs/evidence/2026-10-13-independent-local-consumer.md
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
# Independent local consumer trial — 2026-10-13
|
||||
|
||||
The fresh consumer agent completed the documented Python/HTTP trial against
|
||||
`http://127.0.0.1:18081` without installing Playwright or a browser, reading
|
||||
provider source, or copying sibling fixtures. This proves the **local Docker
|
||||
container** consumer path only. Preflight and every receipt identify
|
||||
`execution_location: workstation-container`, `release: development`, service
|
||||
0.1.0, Playwright 1.64.0 and Chromium 156.0.8078.4. It does not prove a Railiance
|
||||
deployment or accept content-explorer's own task.
|
||||
|
||||
The agent first read the repository instructions and
|
||||
[consumer contract](../consumer-contract.md), then ran:
|
||||
|
||||
```sh
|
||||
python3 client/rapp_playwright.py preflight
|
||||
python3 client/rapp_playwright.py trial --out .local/independent-trial-01
|
||||
```
|
||||
|
||||
The fresh output directory was created by the documented client. Preflight
|
||||
reported browser availability; the retained follow-up
|
||||
[preflight](2026-10-13-independent-local-consumer/preflight.json) records the
|
||||
same runtime identity. The successful run
|
||||
`6b97d75c-0c39-415f-9cf4-f1813c205066` ran from
|
||||
2026-10-13T16:43:17.124Z to 16:43:30.498Z.
|
||||
|
||||
## Evidence
|
||||
|
||||
| Check | Observed result |
|
||||
| --- | --- |
|
||||
| Keyboard and assertions | All 14 steps passed across desktop and narrow contexts: visible heading, count 0, Tab, focused increment button, Enter, count 1, screenshot. |
|
||||
| PNG dimensions | Both desktop files are 1280×800; both narrow files are 390×844. Parsed the PNG IHDR directly and independently recomputed each download SHA-256. |
|
||||
| Screenshot inspection | Narrow final image visibly shows Count: 1 and an orange focus outline around Increment; no horizontal overflow is visible. This is inspection, not automated visual-baseline acceptance. |
|
||||
| Capability boundary | GET of the successful run with a deliberately incorrect bearer token returned HTTP 404 and `not_found`. |
|
||||
| Deliberate UI failure | Run `d6b71146-4f82-4d9c-bf72-4d2b672080d2` expected impossible count 99999: `ui_assertion_failed`, client exit 2, failed desktop step index 1/action text, one prior screenshot preserved. |
|
||||
| Target failure | Run `57b03fc8-4947-43a9-8c9b-058f1c46a9e0` navigated to an intentionally missing path on allowed `http://host.docker.internal:3300`: `target_unreachable`, client exit 3, zero completed steps/artifacts. Receipt does not distinguish HTTP status, connection, DNS or timeout cause. |
|
||||
| Service connection failure | Preflight against unused local service port 18089 returned `service_unreachable: establish the documented tunnel and retry`, exit 3. The real service was not stopped. |
|
||||
| Private handles | Each client-created output directory is 0700 and its run.json is 0600. No run handles or bearer capabilities are copied into retained evidence. |
|
||||
|
||||
Retained evidence:
|
||||
|
||||
- [Successful receipt](2026-10-13-independent-local-consumer/receipt.json),
|
||||
[invocation](2026-10-13-independent-local-consumer/invocation.json),
|
||||
[server trial specification](2026-10-13-independent-local-consumer/trial-spec.json)
|
||||
and [independent artifact checks](2026-10-13-independent-local-consumer/artifact-checks.json).
|
||||
- [Desktop screenshot](2026-10-13-independent-local-consumer/desktop-final.png)
|
||||
and [narrow screenshot](2026-10-13-independent-local-consumer/narrow-final.png).
|
||||
- [Wrong-token response](2026-10-13-independent-local-consumer/wrong-token.json).
|
||||
- [UI failure receipt](2026-10-13-independent-local-consumer/ui-failure-receipt.json),
|
||||
[specification](2026-10-13-independent-local-consumer/failure-spec.json),
|
||||
[invocation](2026-10-13-independent-local-consumer/ui-failure-invocation.json)
|
||||
and [preserved screenshot](2026-10-13-independent-local-consumer/ui-failure-prior-screenshot.png).
|
||||
- [Target failure receipt](2026-10-13-independent-local-consumer/target-failure-receipt.json),
|
||||
[specification](2026-10-13-independent-local-consumer/target-spec.json)
|
||||
and [invocation](2026-10-13-independent-local-consumer/target-failure-invocation.json).
|
||||
- [Client exits, unavailable service probe and permissions](2026-10-13-independent-local-consumer/client-outcomes.json).
|
||||
|
||||
## Usability findings
|
||||
|
||||
1. The first-command path worked without source inspection or dependency setup.
|
||||
Error categories distinguish assertion, target and service connection failure.
|
||||
2. At trial time, the contract's general “Client exit codes” wording did not
|
||||
explain that `status --handle` returns 0 when successfully retrieving a failed
|
||||
run. Both failed runs above reproduced this. A script must inspect JSON status;
|
||||
clarify that command-specific behavior or align its exit codes.
|
||||
3. Target diagnosis identifies the correct failure category but supplies no
|
||||
sanitized reason such as HTTP status or connection failure. The documentation
|
||||
points to browser-network reachability; more precise safe diagnostics would
|
||||
reduce operator round trips. This test makes no claim about which transport
|
||||
cause produced the observed error.
|
||||
4. At initial reading, SCOPE.md, AGENTS.md and workplans/README.md still described
|
||||
a planning-only repository despite the working client and service. The parent
|
||||
delivery session was informed; those status statements need reconciliation
|
||||
before publishing managed availability.
|
||||
|
||||
This report does not establish lifecycle cleanup, cancellation, restart recovery,
|
||||
cross-run browser state isolation, hostile-page safety, a managed endpoint, or
|
||||
consumer application acceptance; those require separate evidence.
|
||||
|
||||
## Final release verification — 2026-10-13, 17:14–17:16 UTC
|
||||
|
||||
Repeated the independent client trial against the corrected immutable release at
|
||||
`http://127.0.0.1:18083`, using fresh client output directories:
|
||||
|
||||
```sh
|
||||
python3 client/rapp_playwright.py --url http://127.0.0.1:18083 preflight
|
||||
python3 client/rapp_playwright.py --url http://127.0.0.1:18083 trial --out .local/independent-final-01
|
||||
python3 client/rapp_playwright.py --url http://127.0.0.1:18083 run --spec .local/independent-final-01/failure-spec.json --out .local/independent-final-ui-failure-01
|
||||
```
|
||||
|
||||
The release recorded by preflight and both receipts is
|
||||
`sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441`.
|
||||
Execution location is `workstation-release-check`; service 0.1.0, Playwright
|
||||
1.64.0 and Chromium 156.0.8078.4 remain unchanged. Preflight lists no allowed
|
||||
external origins, and this repeat used only the provider fixture. No external
|
||||
target, redirect diagnosis or target-unreachable probe was attempted against
|
||||
this fixture-only container.
|
||||
|
||||
Run `35124424-950a-4339-adea-45525b470cab` passed all 14 steps, from
|
||||
2026-10-13T17:14:50.055Z through 17:15:01.861Z, and the client exited 0. Independent
|
||||
PNG header/hash checks again confirmed four screenshots: two at 1280×800 and two
|
||||
at 390×844. Their hashes match the initial successful trial. The run-token
|
||||
rejection probe again returned HTTP 404 / `not_found`. The new output directory
|
||||
is 0700 and its private handle is 0600.
|
||||
|
||||
Run `75c6b50e-94a1-467f-98fc-7e8937f4362e` deliberately failed the count assertion
|
||||
at desktop step index 1 with `ui_assertion_failed`, client exit 2, and the prior
|
||||
1280×800 screenshot preserved with a verified hash. Preflight against unused
|
||||
service port 18089 again produced `service_unreachable` and exit 3. Retrieving the
|
||||
failed run with `--url http://127.0.0.1:18083 status --handle` succeeded with exit 0, now explicitly documented
|
||||
in the consumer contract. AGENTS.md and README.md now describe implemented
|
||||
verification commands and distinguish local evidence from managed availability.
|
||||
|
||||
Recovery usability detail: `--url` must also be repeated on `status` for a
|
||||
nondefault endpoint. The first status invocation omitted it, queried the default
|
||||
18081 service and returned HTTP 404 / exit 3; repeating with the correct 18083
|
||||
override returned the expected failed-run receipt / exit 0. The private handle
|
||||
does not automatically select the originating service URL.
|
||||
|
||||
Retained final evidence:
|
||||
|
||||
- [Preflight](2026-10-13-independent-final-consumer/preflight.json),
|
||||
[successful receipt](2026-10-13-independent-final-consumer/receipt.json),
|
||||
[invocation](2026-10-13-independent-final-consumer/invocation.json),
|
||||
[artifact checks](2026-10-13-independent-final-consumer/artifact-checks.json),
|
||||
[wrong-token response](2026-10-13-independent-final-consumer/wrong-token.json)
|
||||
and [client outcomes](2026-10-13-independent-final-consumer/client-outcomes.json).
|
||||
- [Desktop final](2026-10-13-independent-final-consumer/desktop-final.png),
|
||||
[desktop keyboard](2026-10-13-independent-final-consumer/desktop-6-keyboard-result.png),
|
||||
[narrow final](2026-10-13-independent-final-consumer/narrow-final.png)
|
||||
and [narrow keyboard](2026-10-13-independent-final-consumer/narrow-6-keyboard-result.png).
|
||||
- [Failed assertion receipt](2026-10-13-independent-final-consumer/ui-failure-receipt.json),
|
||||
[specification](2026-10-13-independent-final-consumer/failure-spec.json),
|
||||
[invocation](2026-10-13-independent-final-consumer/ui-failure-invocation.json)
|
||||
and [preserved screenshot](2026-10-13-independent-final-consumer/ui-failure-prior-screenshot.png).
|
||||
|
||||
These are local final-release consumer checks. Read-only root configuration,
|
||||
worker temporary-directory isolation and crash cleanup were reported by the
|
||||
provider; this consumer trial does not independently inspect or establish those
|
||||
properties. Managed platform admission remains pending, and no Railiance
|
||||
deployment or content-explorer acceptance is claimed.
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
[
|
||||
{
|
||||
"name": "desktop-6-keyboard-result.png",
|
||||
"dimensions": [
|
||||
1280,
|
||||
800
|
||||
],
|
||||
"sha256": "b15998824256b86c74f2bbb75f061061be65db0b4f3bb5444529ac69a723cdad",
|
||||
"bytes": 26622
|
||||
},
|
||||
{
|
||||
"name": "desktop-final.png",
|
||||
"dimensions": [
|
||||
1280,
|
||||
800
|
||||
],
|
||||
"sha256": "b15998824256b86c74f2bbb75f061061be65db0b4f3bb5444529ac69a723cdad",
|
||||
"bytes": 26622
|
||||
},
|
||||
{
|
||||
"name": "narrow-6-keyboard-result.png",
|
||||
"dimensions": [
|
||||
390,
|
||||
844
|
||||
],
|
||||
"sha256": "49d27cb169b8e180ce93969c6d2b8619a7684cf035bb9dcae9cd91d8bd9952e8",
|
||||
"bytes": 24280
|
||||
},
|
||||
{
|
||||
"name": "narrow-final.png",
|
||||
"dimensions": [
|
||||
390,
|
||||
844
|
||||
],
|
||||
"sha256": "49d27cb169b8e180ce93969c6d2b8619a7684cf035bb9dcae9cd91d8bd9952e8",
|
||||
"bytes": 24280
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"trial_exit": 0,
|
||||
"intentional_ui_failure_exit": 2,
|
||||
"target_failure_exit": 3,
|
||||
"failed_ui_status_command_exit": 0,
|
||||
"failed_target_status_command_exit": 0,
|
||||
"unavailable_service_preflight": {
|
||||
"argv": [
|
||||
"python3",
|
||||
"client/rapp_playwright.py",
|
||||
"--url",
|
||||
"http://127.0.0.1:18089",
|
||||
"preflight"
|
||||
],
|
||||
"exit": 3,
|
||||
"response": {
|
||||
"error": "service_unreachable: establish the documented tunnel and retry"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
".local/independent-trial-01": {
|
||||
"directory_mode": "0o700",
|
||||
"handle_mode": "0o600"
|
||||
},
|
||||
".local/independent-ui-failure-01": {
|
||||
"directory_mode": "0o700",
|
||||
"handle_mode": "0o600"
|
||||
},
|
||||
".local/independent-target-failure-01": {
|
||||
"directory_mode": "0o700",
|
||||
"handle_mode": "0o600"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 26 KiB |
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"api_version": "1",
|
||||
"target": "fixture",
|
||||
"label": "independent-intentional-ui-failure",
|
||||
"timeout_ms": 30000,
|
||||
"viewports": [
|
||||
{
|
||||
"name": "desktop",
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
}
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"action": "screenshot",
|
||||
"name": "before-failure"
|
||||
},
|
||||
{
|
||||
"action": "text",
|
||||
"selector": "#count",
|
||||
"value": "99999"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"argv": [
|
||||
"client/rapp_playwright.py",
|
||||
"trial",
|
||||
"--out",
|
||||
".local/independent-trial-01"
|
||||
],
|
||||
"service_url": "http://127.0.0.1:18081"
|
||||
}
|
||||
|
After Width: | Height: | Size: 24 KiB |
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"available": true,
|
||||
"error": null,
|
||||
"checked_at": "2026-10-13T16:43:51.651Z",
|
||||
"browser_version": "156.0.8078.4",
|
||||
"api_version": "1",
|
||||
"service_version": "0.1.0",
|
||||
"playwright_version": "1.64.0",
|
||||
"execution_location": "workstation-container",
|
||||
"release": "development",
|
||||
"allowed_origins": [
|
||||
"http://host.docker.internal:3300",
|
||||
"https://onco.cc"
|
||||
],
|
||||
"fixture": true,
|
||||
"active": 1,
|
||||
"queued": 0,
|
||||
"limits": {
|
||||
"concurrency": 1,
|
||||
"max_queue": 4,
|
||||
"max_timeout_ms": 120000,
|
||||
"retention_ms": 3600000,
|
||||
"max_runs": 64
|
||||
}
|
||||
}
|
||||
184
docs/evidence/2026-10-13-independent-local-consumer/receipt.json
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
{
|
||||
"id": "6b97d75c-0c39-415f-9cf4-f1813c205066",
|
||||
"api_version": "1",
|
||||
"status": "passed",
|
||||
"error": null,
|
||||
"label": "independent-browser-trial",
|
||||
"target": "fixture",
|
||||
"viewports": [
|
||||
{
|
||||
"name": "desktop",
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
},
|
||||
{
|
||||
"name": "narrow",
|
||||
"width": 390,
|
||||
"height": 844
|
||||
}
|
||||
],
|
||||
"timeout_ms": 60000,
|
||||
"created_at": "2026-10-13T16:43:17.082Z",
|
||||
"expires_at": "2026-10-13T17:43:17.082Z",
|
||||
"service_version": "0.1.0",
|
||||
"playwright_version": "1.64.0",
|
||||
"release": "development",
|
||||
"execution_location": "workstation-container",
|
||||
"browser_version": "156.0.8078.4",
|
||||
"completed_steps": 14,
|
||||
"steps": [
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 0,
|
||||
"action": "visible",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:43:27.379Z",
|
||||
"selector": "h1"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 1,
|
||||
"action": "text",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:43:27.722Z",
|
||||
"selector": "#count"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 2,
|
||||
"action": "press",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:43:27.838Z",
|
||||
"key": "Tab"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 3,
|
||||
"action": "focused",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:43:27.884Z",
|
||||
"selector": "#increment"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 4,
|
||||
"action": "press",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:43:28.011Z",
|
||||
"key": "Enter"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 5,
|
||||
"action": "text",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:43:28.027Z",
|
||||
"selector": "#count"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 6,
|
||||
"action": "screenshot",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:43:28.052Z"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 0,
|
||||
"action": "visible",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:43:29.234Z",
|
||||
"selector": "h1"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 1,
|
||||
"action": "text",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:43:29.476Z",
|
||||
"selector": "#count"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 2,
|
||||
"action": "press",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:43:29.640Z",
|
||||
"key": "Tab"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 3,
|
||||
"action": "focused",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:43:29.663Z",
|
||||
"selector": "#increment"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 4,
|
||||
"action": "press",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:43:29.757Z",
|
||||
"key": "Enter"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 5,
|
||||
"action": "text",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:43:29.773Z",
|
||||
"selector": "#count"
|
||||
},
|
||||
{
|
||||
"viewport": "narrow",
|
||||
"index": 6,
|
||||
"action": "screenshot",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:43:29.814Z"
|
||||
}
|
||||
],
|
||||
"artifacts": [
|
||||
{
|
||||
"name": "desktop-6-keyboard-result.png",
|
||||
"bytes": 26622
|
||||
},
|
||||
{
|
||||
"name": "desktop-final.png",
|
||||
"bytes": 26622
|
||||
},
|
||||
{
|
||||
"name": "narrow-6-keyboard-result.png",
|
||||
"bytes": 24280
|
||||
},
|
||||
{
|
||||
"name": "narrow-final.png",
|
||||
"bytes": 24280
|
||||
}
|
||||
],
|
||||
"started_at": "2026-10-13T16:43:17.124Z",
|
||||
"blocked_requests": 0,
|
||||
"finished_at": "2026-10-13T16:43:30.498Z",
|
||||
"downloads": [
|
||||
{
|
||||
"name": "desktop-6-keyboard-result.png",
|
||||
"bytes": 26622,
|
||||
"sha256": "b15998824256b86c74f2bbb75f061061be65db0b4f3bb5444529ac69a723cdad"
|
||||
},
|
||||
{
|
||||
"name": "desktop-final.png",
|
||||
"bytes": 26622,
|
||||
"sha256": "b15998824256b86c74f2bbb75f061061be65db0b4f3bb5444529ac69a723cdad"
|
||||
},
|
||||
{
|
||||
"name": "narrow-6-keyboard-result.png",
|
||||
"bytes": 24280,
|
||||
"sha256": "49d27cb169b8e180ce93969c6d2b8619a7684cf035bb9dcae9cd91d8bd9952e8"
|
||||
},
|
||||
{
|
||||
"name": "narrow-final.png",
|
||||
"bytes": 24280,
|
||||
"sha256": "49d27cb169b8e180ce93969c6d2b8619a7684cf035bb9dcae9cd91d8bd9952e8"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"argv": [
|
||||
"client/rapp_playwright.py",
|
||||
"run",
|
||||
"--spec",
|
||||
".local/independent-trial-01/target-spec.json",
|
||||
"--out",
|
||||
".local/independent-target-failure-01"
|
||||
],
|
||||
"service_url": "http://127.0.0.1:18081"
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"id": "57b03fc8-4947-43a9-8c9b-058f1c46a9e0",
|
||||
"api_version": "1",
|
||||
"status": "failed",
|
||||
"error": "target_unreachable",
|
||||
"label": "independent-target-http-failure",
|
||||
"target": "http://host.docker.internal:3300/independent-trial-deliberately-missing-page-20261013",
|
||||
"viewports": [
|
||||
{
|
||||
"name": "desktop",
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
}
|
||||
],
|
||||
"timeout_ms": 30000,
|
||||
"created_at": "2026-10-13T16:44:48.609Z",
|
||||
"expires_at": "2026-10-13T17:44:48.609Z",
|
||||
"service_version": "0.1.0",
|
||||
"playwright_version": "1.64.0",
|
||||
"release": "development",
|
||||
"execution_location": "workstation-container",
|
||||
"browser_version": "156.0.8078.4",
|
||||
"completed_steps": 0,
|
||||
"steps": [],
|
||||
"artifacts": [],
|
||||
"started_at": "2026-10-13T16:44:48.614Z",
|
||||
"blocked_requests": 0,
|
||||
"finished_at": "2026-10-13T16:45:11.290Z",
|
||||
"downloads": []
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"api_version": "1",
|
||||
"target": "http://host.docker.internal:3300/independent-trial-deliberately-missing-page-20261013",
|
||||
"label": "independent-target-http-failure",
|
||||
"timeout_ms": 30000,
|
||||
"viewports": [
|
||||
{
|
||||
"name": "desktop",
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
}
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"action": "visible",
|
||||
"selector": "h1"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"api_version": "1",
|
||||
"target": "fixture",
|
||||
"label": "independent-browser-trial",
|
||||
"steps": [
|
||||
{
|
||||
"action": "visible",
|
||||
"selector": "h1"
|
||||
},
|
||||
{
|
||||
"action": "text",
|
||||
"selector": "#count",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"action": "press",
|
||||
"key": "Tab"
|
||||
},
|
||||
{
|
||||
"action": "focused",
|
||||
"selector": "#increment"
|
||||
},
|
||||
{
|
||||
"action": "press",
|
||||
"key": "Enter"
|
||||
},
|
||||
{
|
||||
"action": "text",
|
||||
"selector": "#count",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"action": "screenshot",
|
||||
"name": "keyboard-result"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"argv": [
|
||||
"client/rapp_playwright.py",
|
||||
"run",
|
||||
"--spec",
|
||||
".local/independent-trial-01/failure-spec.json",
|
||||
"--out",
|
||||
".local/independent-ui-failure-01"
|
||||
],
|
||||
"service_url": "http://127.0.0.1:18081"
|
||||
}
|
||||
|
After Width: | Height: | Size: 25 KiB |
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"id": "d6b71146-4f82-4d9c-bf72-4d2b672080d2",
|
||||
"api_version": "1",
|
||||
"status": "failed",
|
||||
"error": "ui_assertion_failed",
|
||||
"label": "independent-intentional-ui-failure",
|
||||
"target": "fixture",
|
||||
"viewports": [
|
||||
{
|
||||
"name": "desktop",
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
}
|
||||
],
|
||||
"timeout_ms": 30000,
|
||||
"created_at": "2026-10-13T16:44:28.954Z",
|
||||
"expires_at": "2026-10-13T17:44:28.954Z",
|
||||
"service_version": "0.1.0",
|
||||
"playwright_version": "1.64.0",
|
||||
"release": "development",
|
||||
"execution_location": "workstation-container",
|
||||
"browser_version": "156.0.8078.4",
|
||||
"completed_steps": 1,
|
||||
"steps": [
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 0,
|
||||
"action": "screenshot",
|
||||
"status": "passed",
|
||||
"at": "2026-10-13T16:44:41.184Z"
|
||||
},
|
||||
{
|
||||
"viewport": "desktop",
|
||||
"index": 1,
|
||||
"action": "text",
|
||||
"status": "failed",
|
||||
"at": "2026-10-13T16:44:41.508Z",
|
||||
"selector": "#count"
|
||||
}
|
||||
],
|
||||
"artifacts": [
|
||||
{
|
||||
"name": "desktop-0-before-failure.png",
|
||||
"bytes": 25740
|
||||
}
|
||||
],
|
||||
"started_at": "2026-10-13T16:44:36.279Z",
|
||||
"blocked_requests": 0,
|
||||
"finished_at": "2026-10-13T16:44:46.861Z",
|
||||
"downloads": [
|
||||
{
|
||||
"name": "desktop-0-before-failure.png",
|
||||
"bytes": 25740,
|
||||
"sha256": "141dcadca4774da5fefec835bb789c5221685d3f453070a593e69aa6ad4782ce"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"http_status": 404,
|
||||
"body": {
|
||||
"error": "not_found"
|
||||
}
|
||||
}
|
||||
9
docs/evidence/2026-10-13-missing-browser.log
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
✔ missing browser fails preflight and prevents admission (3280.374816ms)
|
||||
ℹ tests 1
|
||||
ℹ suites 0
|
||||
ℹ pass 1
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 4049.467658
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "e77247e5-ce25-4711-a64f-af81a9f7cbb1",
|
||||
"from_agent": "rapp-playwright",
|
||||
"to_agent": "railiance-platform",
|
||||
"subject": "Browser package ready locally; admission and Forgejo OCI upload blockers",
|
||||
"body": "Founder explicitly selected railiance-platform lifecycle ownership. Final source implementation and independent client evidence are in rapp-playwright; candidate local OCI index sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441, Playwright1.64.0/Chromium156.0.8078.4. Read-only Docker integration4/4 plus missing-browser negative1/1; independent final trial passed. No cluster mutation. Current node92.205.62.239 Ready, requests3460m/4CPU and9472Mi; package100m/256Mi limit1CPU/1Gi,2GiPVC,loopbackRBAC,Recreate. OCI publication to forgejo.coulomb.social/coulomb/rapp-playwright:0.1.0 failed repeatedly at final blob PUT: HTTP499 then500, including digests9a29e489bcc76cee14bece1a90741a2a7512f0c69ffefbe7f5d17500f24d8ba5 and071058c704d16066a57f826f0df652f0f8780f9e825cbf4242946b68f4fa79e0. Smaller layers uploaded; manifest publication not established. Please confirm admission/access lane and diagnose registry large-blob failures in owner scope. WP0003-T01 and WP0004-T01 remain explicit waits. Evidence/docs and sync commit will be in repo; no secret or credential request.",
|
||||
"thread_id": "c82d75f3-3191-410d-a8c8-fd647bf7f3ef",
|
||||
"read_at": null,
|
||||
"archived_at": null,
|
||||
"created_at": "2026-10-13T17:18:22.948833Z",
|
||||
"kind": "message",
|
||||
"expires_at": null,
|
||||
"supersedes_id": null,
|
||||
"reader": null,
|
||||
"delivered_at": null,
|
||||
"acknowledged_at": null
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "30503ee1-77f9-4e9e-a63a-2f70ac097823",
|
||||
"from_agent": "rapp-playwright",
|
||||
"to_agent": "reuse-surface",
|
||||
"subject": "rapp-playwright implemented: independent local consumer evidence; managed admission pending",
|
||||
"body": "WP-0004 provider handoff: service and Python stdlib client implemented in /home/worsch/rapp-playwright. docs/consumer-contract.md is the v1 command/API contract. docs/evidence/2026-10-13-independent-local-consumer.md retains independent successful fixture, two viewports, keyboard evidence, wrong-token/UI/target/service negatives. docs/content-explorer-pilot.md maps the pinned OnCo home-page pilot. make test-integration now passes real Chromium isolation, proxy redirects, cancellation/timeout/crash/restart/expiry under read-only Docker. Playwright 1.64.0 / Chromium 156.0.8078.4. Founder selected railiance-platform lifecycle ownership; managed admission remains pending (c82d75f3-3191-410d-a8c8-fd647bf7f3ef), so do not claim Railiance availability. Please update existing capability.infotech.headless-browser with local verified provider contract/evidence and the conditional managed command, then return published discovery readback. Keep source-only fallback explicit. Source commit/immutable image evidence will follow in docs/evidence. No duplicate capability.",
|
||||
"thread_id": null,
|
||||
"read_at": null,
|
||||
"archived_at": null,
|
||||
"created_at": "2026-10-13T17:12:22.927559Z",
|
||||
"kind": "message",
|
||||
"expires_at": null,
|
||||
"supersedes_id": null,
|
||||
"reader": null,
|
||||
"delivered_at": null,
|
||||
"acknowledged_at": null
|
||||
}
|
||||
71
docs/operations.md
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Service operations
|
||||
|
||||
Build with `make install test build`. `make test-integration` launches real Chromium
|
||||
inside the pinned upstream image; it needs Docker, not a host browser. Publish
|
||||
the tested image through the existing Forgejo OCI credential lane and read its
|
||||
digest back. `make render IMAGE_REFERENCE=forgejo.coulomb.social/coulomb/rapp-playwright@sha256:…`
|
||||
rejects floating tags. Never record a placeholder as a published digest.
|
||||
|
||||
The initial package uses a single replica, Recreate updates, 100m CPU/256Mi
|
||||
requests, 1 CPU/1Gi limits, and a 2Gi local-path PVC. Artifacts expire after one
|
||||
hour; they are transient evidence, not a backup service. Consumers download
|
||||
what they need to retain. Recreate avoids a surge on the capacity-constrained
|
||||
single-node cluster and preserves the RWO volume.
|
||||
|
||||
Before activation, confirm the node identity and current resource headroom,
|
||||
platform lifecycle ownership/admission, publication/pull access, and private
|
||||
consumer connectivity. Run `make server-dry-run IMAGE_REFERENCE=…`, inspect
|
||||
`kubectl diff -f .rendered/workload.yaml` on railiance01, then deploy the reviewed
|
||||
manifest with `make deploy IMAGE_REFERENCE=…`. No existing workload or policy
|
||||
needs relaxation. Platform confirmation is tracked in WP-0004-T01.
|
||||
|
||||
For the first installation, after admission, run `make bootstrap-namespace
|
||||
IMAGE_REFERENCE=…` before the full server dry run. Kubernetes does not persist
|
||||
a dry-run Namespace, so namespace-scoped objects cannot be server-validated
|
||||
until the namespace exists. This bootstrap dry-runs and diffs only the reviewed
|
||||
Namespace before creating it. The full deployment also diffs before applying.
|
||||
|
||||
The `playwright-consumer` Role grants only pod discovery and port-forward. Bind
|
||||
an approved existing user/group through the identity/platform owner. This repo
|
||||
does not mint credentials or silently bind all authenticated users. Callers with
|
||||
existing authorized Kubernetes access can use the documented port-forward.
|
||||
The pod has no service-account token and no cluster permissions.
|
||||
|
||||
Readiness performs the authenticated-protocol preflight over pod loopback. It
|
||||
requires a successful browser launch; liveness checks the process only. The pod
|
||||
uses non-root UID/GID 1000, read-only root, dropped capabilities and RuntimeDefault
|
||||
seccomp. Tini reaps orphaned browser processes. Chromium's OS sandbox is disabled
|
||||
for this trusted-target pilot; it is not approved for hostile websites or
|
||||
mutually untrusted tenants. The HTTP/CONNECT origin gate, private-address checks,
|
||||
blocked workers/WebSockets, per-run process/context and resource limits cover
|
||||
the documented application-level boundary, not browser-kernel exploits.
|
||||
|
||||
## Verify and recover
|
||||
|
||||
After establishing the consumer tunnel, `make verify-live TRIAL_OUT=.local/live-N`
|
||||
must produce four PNGs, focus/keyboard assertions and a receipt. Check the live
|
||||
container image and imageID against the published digest, PVC binding, deny-all
|
||||
ingress and ready replica. Exercise wrong run token, deliberate UI failure,
|
||||
unreachable target, cancellation and service restart; a passing health endpoint
|
||||
alone is insufficient.
|
||||
|
||||
After restart, accepted nonterminal runs become `interrupted`; completed runs
|
||||
remain retrievable until expiry. A client disconnect does not cancel an accepted
|
||||
job, so all runs have enforced deadlines. Supervisor cancellation closes the
|
||||
browser; after two seconds it kills the recorded worker/browser process groups.
|
||||
Service shutdown drains/cancels bounded work. Corrupt receipts cause startup to
|
||||
fail closed; restore or quarantine the affected transient run directory through
|
||||
the owner, without exposing capability hashes or screenshots in logs.
|
||||
|
||||
Rollback: retain the previous immutable image reference and rendered revision,
|
||||
review its schema compatibility, then run `make rollback IMAGE_REFERENCE=…`.
|
||||
Verify the replacement's imageID and repeat the trial. For the first release,
|
||||
test recovery by reapplying the same verified digest; do not call that proof of
|
||||
cross-version rollback. Once a second release exists, exercise an actual digest
|
||||
change and return to the reviewed release. No automated rollback migrates or
|
||||
deletes the PVC. V1 receipts are JSON; future incompatible formats require an
|
||||
explicit migration/rollback decision.
|
||||
|
||||
Upgrade Playwright and its base image together, update the lockfile, repeat the
|
||||
unit/browser/client tests and consumer pilot, then publish a new immutable image.
|
||||
The Python client checks protocol v1 and does not install Playwright.
|
||||