Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
74ba171
doc: notice identity docs, README pointer, and CHANGELOG entry (issue…
s-t-e-f-a-n Oct 30, 2026
af5d542
doc: plans — PR #41: DELIVERED (S7 complete: Gitea main 74ba171, GitH…
s-t-e-f-a-n Oct 30, 2026
925223f
doc: plans — PR #41: finalize S7 record (DELIVERED status + S7 execut…
s-t-e-f-a-n Oct 30, 2026
8c9a17d
doc: plans — PR #53: S1+S2 review plan doc (verdict approve with M1 C…
s-t-e-f-a-n Oct 30, 2026
f2bb4ee
doc: plans — PR #53: S3 gate passed (M1 as-is) + S4 integration appli…
s-t-e-f-a-n Oct 30, 2026
4fddfbc
doc: plans — PR #53: S5 validation all green (PR tests 24p/15 subtest…
s-t-e-f-a-n Oct 30, 2026
e730206
doc: plans — PR #53: S6 docs verified (CHANGELOG M1 in place, no READ…
s-t-e-f-a-n Oct 30, 2026
9ccb1ff
fix: parse numeric cron weekdays with crontab numbering in scheduler …
s-t-e-f-a-n Oct 30, 2026
44c693a
test: cover crontab weekday numbering and UI/scheduler parity (#46)
s-t-e-f-a-n Oct 30, 2026
fa1ade5
doc: changelog — crontab weekday numbering fix (#46)
s-t-e-f-a-n Oct 30, 2026
35b4e18
doc: plans — PR #53: S7 complete (DELIVERED: Gitea main fa1ade5, GitH…
s-t-e-f-a-n Oct 30, 2026
30839f4
doc: plans — PR #54: S1+S2 review (verdict approve with M1 CHANGELOG …
s-t-e-f-a-n Oct 30, 2026
fb82e95
doc: plans — PR #54: S3 gate passed (proposal as-is) + S4 integration…
s-t-e-f-a-n Oct 30, 2026
f5be955
doc: plans — PR #54: S5 validated (PR module 14 passed, before/after …
s-t-e-f-a-n Oct 31, 2026
e61f2df
doc: plans — PR #54: S6 docs confirmed (CHANGELOG M1 entry stands und…
s-t-e-f-a-n Oct 31, 2026
f68a663
fix: rebuild persistent scheduler jobs from Task before resume (#47)
s-t-e-f-a-n Oct 31, 2026
f163369
test: cover start() dropping persisted disabled and deleted jobs (#47)
s-t-e-f-a-n Oct 31, 2026
a75861f
doc: changelog — persistent scheduler job rebuild fix (#47)
s-t-e-f-a-n Oct 31, 2026
497dbdb
doc: plans — PR #54: S7 complete (DELIVERED: Gitea main a75861f, GitH…
s-t-e-f-a-n Oct 31, 2026
a96fabe
doc: plans — PR #56: S1+S2 review (verdict approve with M1 CHANGELOG …
s-t-e-f-a-n Oct 31, 2026
e3844d4
doc: plans — PR #56: S5 results (M2 test_bc_login post-count adaptati…
s-t-e-f-a-n Oct 31, 2026
da01d5b
feat: notify terminal daemon run failures through the opt-in ntfy top…
s-t-e-f-a-n Oct 31, 2026
32140f7
test: cover terminal run notifications and adapt the slow-login post-…
s-t-e-f-a-n Oct 31, 2026
2bf53d8
doc: changelog + README — terminal run notifications (#49)
s-t-e-f-a-n Oct 31, 2026
19c4164
doc: plans — PR #56: S7 complete (DELIVERED: Gitea main 2bf53d8, GitH…
s-t-e-f-a-n Oct 31, 2026
53bed6b
doc: plans — PR #55: S1+S2 review (verdict approve with M1 CHANGELOG …
s-t-e-f-a-n Oct 31, 2026
eedbed7
doc: plans — PR #55: S3 confirmed as-is + S4 integration applied (5/6…
s-t-e-f-a-n Oct 31, 2026
da1aa40
doc: plans — PR #55: S5 all green on exact dev tree (before/after 8/8…
s-t-e-f-a-n Oct 31, 2026
47e1c81
doc: plans — PR #55: S6 docs confirmed (CHANGELOG M1 stands, vault_tr…
s-t-e-f-a-n Oct 31, 2026
f4f1a05
fix: resolve the exact vault item id before credential reads (#48)
s-t-e-f-a-n Oct 31, 2026
79d22b1
test: cover exact vault item identity and the query-preserving transp…
s-t-e-f-a-n Oct 31, 2026
3c950d7
doc: changelog + vault transport doc — exact vault item identity (#48)
s-t-e-f-a-n Oct 31, 2026
22da847
doc: changelog + vault transport doc — exact vault item identity (#48)
s-t-e-f-a-n Oct 31, 2026
f24c154
Sync accepted upstream fixes while preserving fork contracts
flowcool Oct 31, 2026
eebb902
doc: plans — PR #55: S7 DELIVERED (dev/main 22da847, GitHub 3c950d7);…
s-t-e-f-a-n Oct 31, 2026
0a10554
Align the synchronized fork and image qualification with pypdf securi…
flowcool Oct 31, 2026
90516a0
build(deps): bump the python-updates group across 2 directories with …
dependabot[bot] Oct 31, 2026
41c38b7
doc: plans — PR #45: S1+S2 review (verdict approve with M1 CHANGELOG …
s-t-e-f-a-n Oct 31, 2026
515155f
Merge pull request #85 from flowcool/sync/upstream-accepted-fixes-202…
flowcool Oct 31, 2026
03c9123
doc: plans — PR #45: S3+S4 (proposal confirmed as-is; S4 applied all …
s-t-e-f-a-n Oct 31, 2026
1a50b9d
doc: plans — PR #45: S5+S6 (validation all green on the exact dirty t…
s-t-e-f-a-n Oct 31, 2026
b4e833f
fix: preserve the explicit Playwright browser cache on import
s-t-e-f-a-n Oct 31, 2026
430146c
fix: classify sandbox launch banners without retaining private logs
s-t-e-f-a-n Oct 31, 2026
96871d1
doc: changelog — browser cache precedence and safe launch diagnostics
s-t-e-f-a-n Oct 31, 2026
31da0dc
doc: plans — PR #45: S7 DELIVERED (dev/main 96871d1, GitHub 8f4bdd9);…
s-t-e-f-a-n Nov 1, 2026
5f0063f
doc: plans — PR #44: S1+S2 review (verdict approve; head 0b9a57f stab…
s-t-e-f-a-n Nov 1, 2026
83804b5
doc: plans — PR #44: S4 INTEGRATING (16 files applied byte-for-byte f…
s-t-e-f-a-n Nov 1, 2026
6d16c18
doc: plans — PR #44: S5 VALIDATED (preflight re-verified: head 0b9a57…
s-t-e-f-a-n Nov 1, 2026
d9e8c4a
doc: plans — PR #44: S6 docs (README method-chain validation touch-up…
s-t-e-f-a-n Nov 1, 2026
58d7a4b
feat: validate recipe method chains before vault or browser access (C…
s-t-e-f-a-n Nov 1, 2026
df634cc
test: cover daemon recipe method-chain validation (C3, issue #27)
s-t-e-f-a-n Nov 1, 2026
4a9bffc
doc: method-chain contract, README validation note, changelog (C3, is…
s-t-e-f-a-n Nov 1, 2026
0ffd3e4
sync fork with upstream C3 and browser cache integration
flowcool Nov 1, 2026
3ca8112
Merge pull request #87 from flowcool/sync/upstream-c3-cache-20261101
flowcool Nov 1, 2026
41a25bf
feat: integrate the reviewed catalog adapter without changing legacy …
flowcool Nov 1, 2026
ecec703
fix: preflight adapter month budgets and address Pullfrog contract fi…
flowcool Nov 1, 2026
dd663c8
Merge pull request #90 from flowcool/feature/playwright-bd-boundary-84
flowcool Nov 1, 2026
2b3f8ee
fix: accept optional native login phase transitions
flowcool Nov 1, 2026
4a13b7e
test: include asyncio metadata and mode in image login checks
flowcool Nov 1, 2026
000ffa1
review: share phase markers and map optional-login proof
flowcool Nov 1, 2026
4a6e981
Merge pull request #91 from flowcool/feature/playwright-native-login-85
flowcool Nov 1, 2026
7b3f79b
fix: keep pyee compatible while refreshing dependencies
flowcool Nov 2, 2026
4898cc5
Merge Dependabot PR86 history with the qualified compatible pins
flowcool Nov 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/requirements-security.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
bandit==1.9.4
pip-audit==2.10.1
ruff==0.17.1
ruff==0.17.3
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Fixed

- Native phased login accepts final sessions after any submission and skips
optional phases when a later state is proven. Preserve one durable attempt,
fresh OTP resolution and verification-only resume.

### Changed

- Refresh runtime and test dependency pins, including Playwright 1.64.1.
Keep pyee 13.0.1 within Playwright's required >=13,<15 range so the
dependency batch installs successfully.

- Update pypdf to 6.21.4 for seven security fixes. Align the packaged image
qualifier and parser documentation; preserve the `pdf-graph-v1` revision rule.

- Separate durable login and document request pacing via generic engine gates. Preserve the Bourse Direct 60-second login floor; configure document pacing through Settings (default 5 seconds). No DB or recipe schema change.
- Download All uses the recipe history limit; incremental collection keeps its
task date window. Install the Bourse Direct recipe from the recipe catalog.
Expand All @@ -29,6 +42,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Added

- Explicit `bourse_direct_adapter` catalog boundary with native login, fixed run
dates, C3 load/save/run-start validation, frozen trusted source and bounded
authenticated original reads. Existing `bourse_direct` catalogs keep their
current contract. The additive recipe schema must be compared and explicitly
promoted by Infra before a new catalog action is adopted. Provider
qualification and deployment remain separate.

- One Infra runtime contract for consume-folder delivery, exact vault lookup,
schema promotion, DB/configuration handoffs and daemon-path qualification.
Provider docs and harness boundaries now refer to it. No runtime, recipe-schema
Expand Down
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ COPY --chown=${APP_UID}:${APP_GID} apps/. .
# non-root app user
RUN mkdir -p /apps/Downloads "$HOME" && chown -R ${APP_UID}:${APP_GID} /apps "$HOME"

# Trusted catalog application code must not be group/world writable.
RUN find /apps/recipes_playwright -type f -name 'bourse_direct_adapter.py' \
-exec chmod go-w {} +

# Keep the schema available when recipes are mounted as mutable configuration.
COPY apps/recipes_playwright/recipe-pw-schema.yaml /opt/billcollector/recipe-pw-schema.yaml

Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Compose service includes a pinned seccomp profile. See
- [Vault of Secrets](#vault-of-secrets)
- [Enabling DNS and HTTPS with Let's Encrypt certs](#enabling-dns-and-https-with-lets-encrypt-certs)
- [Installing the Daemon](#installing-the-daemon)
- [Pause notifications (ntfy)](#pause-notifications-ntfy)
- [Notifications (ntfy)](#notifications-ntfy)
- [Daemon Mode](#daemon-mode)
- [Environment Variables](#environment-variables)
- [Container Semantics](#container-semantics)
Expand Down Expand Up @@ -224,7 +224,7 @@ Now that we have done a good job installing all the prerequisites, we are focusi

8. Collect your first document: on the **Dashboard** press **Run now** for an enabled task - watch the run live (session page, noVNC tab) until it reaches `success`; the document appears on the Downloads page and in the consumer folder. Every step is driven from the browser, no shell access to the daemon needed.

### Pause notifications (ntfy)
### Notifications (ntfy)

A fresh installation sends no notifications: the `ntfy topic` setting (`ntfy_topic` in Settings) starts empty. Paused runs, blocked logins, and failed or interrupted daemon runs only push when you set a topic. To opt in, open **Settings** and set an **ntfy topic** - choose your own topic name, it is the install-specific destination the pushes are sent to.

Expand Down Expand Up @@ -298,7 +298,7 @@ All pages require sign-in (a session cookie set on `/login`).
- **Dashboard** (`/`) - all tasks with their status (color-coded, `waiting` rows highlighted), last/next run, latest download, a Login column (available / pending / blocked for tasks with a phased login), and a `[N waiting]` badge for paused runs; a live log pane following the active run and a step-screenshot strip; per row **Run now** (works for disabled tasks as a manual one-off, schedule untouched; disabled while the login is blocked), **Stop**, and **Edit** (cron + enabled); toolbar **Run (filtered)** (enqueues all enabled tasks in the filtered set, skipping blocked ones) and **Add task**.
- **Task detail** (`/tasks/<id>`) - the run history of one task with the per-step records, step screenshots, and downloads; for a phased login the budget state (unproven attempts / limit) and, while idle and locked, a **Clear login block** button (grants exactly one further attempt, profile and schedule preserved).
- **Session** (`/runs/<id>`) - the live view of one run (the ntfy deep-link target): while it runs, you watch the progress; while it is paused, you get the "Copilot" view - a screenshot, the numbered interactive elements, click/fill/press/drag actions, and Resume / Cancel - plus a noVNC tab that shows the actual browser window. A step that failed and paused also offers a Repair link into the recipe editor.
- **Recipe editor** (`/recipes/<service>`) - edit the YAML recipe with schema validation, and optionally auto-commit the change to git (Settings switch).
- **Recipe editor** (`/recipes/<service>`) - edit the YAML recipe with schema and method-chain validation (see [recipe method contract](doc/RECIPE_METHODS.md)), and optionally auto-commit the change to git (Settings switch).
- **Downloads** (`/downloads`) - the downloaded files of all runs; a file whose sha256 was already seen for the same task is marked as a duplicate (the consumer hook is not re-run for it). See [download recovery](doc/DOWNLOAD_RECOVERY.md) for staged publication, recovery and the at-least-once consumer contract.
- **Settings** (`/settings`) - UI password, ntfy topic, pause deadline, max concurrency, display start, download directory, UI base URL, VNC password, the vault API URL/host, and the git auto-commit switch. Each field writes its own Settings row on save, so an invalid value only fails that field.

Expand Down Expand Up @@ -356,7 +356,8 @@ The BillCollector configuration for each web service from which you want to retr
- One recipe per web portal identified by the yaml element `serviceName` and its filename `recipe-pw__<serviceName>`.
- Each recipe is structured in numbered `steps`, executed in ascending `step` order.
- Each step is a chain of `methods`: Playwright Page/Locator calls executed in order. Locator methods (`locator` for CSS selectors, `get_by_role`, `get_by_label`, `get_by_text`, `get_by_title`, `get_by_placeholder`, `get_by_test_id`) yield a locator that is chained into the following action (`click`, `fill`, `press`, `first`). Page-level methods include `goto` (open the start URL), `content_frame` (enter an iframe) and `close`.
- Downloads: a step with the `expect_download` method wraps the nested `steps` that trigger the download; the file is saved into the Downloads folder. A re-downloaded file that is byte-identical to one already tracked for the same task (same sha256) is recorded as a duplicate and the consumer hook is not re-run for it.
- Downloads: a step with the `expect_download` method wraps the nested `steps` that trigger the download; the file is saved into the Downloads folder. A re-downloaded file that is byte-identical to one already tracked for the same task (same sha256) is recorded as a duplicate and the consumer hook is not re-run for it. An opt-in `notice_identity` step option switches a download step from byte-hash deduplication to source-scoped identity plus a conservative PDF revision digest; see [notice identity](doc/NOTICE_IDENTITY.md).
- Catalog adapter: use the standalone daemon-only [`bourse_direct_adapter` action](doc/BOURSE_DIRECT_ADAPTER.md) after native login. The existing locator-prefixed `bourse_direct` catalog contract remains separate.
- Bounded pagination: use one page locator followed by `click_until_absent`. Its single options object requires `progress_selector`, `max_clicks`, and `timeout_ms`. `timeout_ms` is the total browser-action budget. Every click must increase the visible progress count or make the pager absent or hidden. This action supports cumulative "load more" controls. It rejects page-replacement pagination and virtualized lists because their visible item count does not increase. For monthly replacement calendars, use the daemon's [replacement_calendar action](doc/REPLACEMENT_CALENDARS.md).
- Bounded collections: use one page locator followed by `download_all`. Its single options object requires `key_attribute`, `max_items`, `timeout_ms`, and `event_window_ms`. `timeout_ms` bounds the browser actions and observations for the whole collection. Persistence (`save_as`, hashing, consumers, and SQLite) remains synchronous and cannot be preempted by that browser deadline. `event_window_ms` is the full attribution window observed after each click, so `timeout_ms` must exceed the actual item count multiplied by `event_window_ms`, with additional time for browser actions. The engine reports the required and remaining budgets before its first click when the minimum attribution-window budget cannot fit. The key must be non-empty and unique for every item. The ordered key set must remain stable while each control produces exactly one download. `download_all` also rejects a suggested filename already saved earlier in the run instead of overwriting it.
- Incremental collections: `"incremental": true` in the `download_all` options records each item key (`<key_attribute>=<key>`) with its download. Later runs of the same task skip known keys without clicking them, so only the first run collects everything. Use it only with a key that is stable per document across sessions (for example an invoice number); a constant or session-bound key misses or repeats documents. `"unpack": "pdf"` publishes the single PDF member of a ZIP download unchanged under its member name and deduplicates on the PDF; any other archive content fails without publication. Both options require the daemon runner.
Expand Down Expand Up @@ -386,8 +387,8 @@ The BillCollector configuration for each web service from which you want to retr
exact-key credential substitution remain unchanged.
- `graceful: true` on a step: a failure of that step is recorded (log + DB) and the run continues with the next step; without it, a failure aborts the service's run (after three attempts, in daemon mode the run pauses for your help instead - see [Using the Web UI](#using-the-web-ui)).
- A `wait_user` step: an explicit pause point for operator input (e.g., a CAPTCHA you have to solve by hand). It can carry an optional `prompt` and an optional `until` locator (the pause completes when that element appears, or on resume); the step is consumed, not re-executed.
- There is a YAML schema named `recipe-pw-schema.yaml` which includes the rules to be followed by the YAML recipes.
- In daemon mode the recipe can also be edited in the UI [recipe editor](#using-the-web-ui) (schema validation, optional git auto-commit) - handy when a portal changes and a paused run links you straight to the failing recipe.
- There is a YAML schema named `recipe-pw-schema.yaml` which includes the rules to be followed by the YAML recipes. In daemon mode, validation goes beyond the schema: every method chain is checked against the installed Playwright (method names, argument names and types, properties, reserved `wait_user` options), and unknown methods and unsupported variables are rejected before vault or browser access, with errors that identify the service and step without argument values - the supported surface is documented in the [recipe method contract](doc/RECIPE_METHODS.md).
- In daemon mode the recipe can also be edited in the UI [recipe editor](#using-the-web-ui) (schema and method-chain validation, optional git auto-commit) - handy when a portal changes and a paused run links you straight to the failing recipe.

> 💡 **Tip**
> When creating new recipes, make use of AI, e.g., let yourself be helped by Copilot - that speeds up creating the YAML recipe 🚀.
Expand Down
4 changes: 2 additions & 2 deletions apps/BillCollectorServices_pw.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ def run_step(step, *, nested=False):
if isinstance(entry, dict)
}
if nested and methods & {
"click_until_absent", "download_all", "replacement_calendar"
"click_until_absent", "download_all", "replacement_calendar", "bourse_direct_adapter"
}:
raise StepAbortError(
"bounded collection actions cannot be nested "
Expand Down Expand Up @@ -459,7 +459,7 @@ def transform_step_to_json(step):

if graceful and any(
isinstance(entry, dict)
and entry.get("method") in {"click_until_absent", "download_all", "replacement_calendar"}
and entry.get("method") in {"click_until_absent", "download_all", "replacement_calendar", "bourse_direct_adapter"}
for entry in methods
):
message = "bounded collection actions cannot be graceful"
Expand Down
Loading