# Changelog of Florida New Business Filings - Sunbiz Daily Feed (`fecklesspleb/florida-sunbiz-daily-filings`) Actor

- **URL**: https://apify.com/fecklesspleb/florida-sunbiz-daily-filings/changelog.md
- **Full Actor documentation**: https://apify.com/fecklesspleb/florida-sunbiz-daily-filings.md

## Changelog

### Unreleased, 2026-11-04 (local, not built or pushed; actor.json version unchanged: 0.1): invalid input ends SUCCEEDED with a free status row

- **Invalid input no longer FAILS the run.** Input that `parse_input` rejects (e.g. `recordTypes: ["liens"]`, a bad date, both `date` and `startDate`, a range over 31 days, `status: "dead"`, `maxResults: -1`) used to call `Actor.fail` (run FAILED, exit 1, status message `Invalid input: <reason>`). The run now ends SUCCEEDED (exit 0) with one free row in this Actor's existing status-row shape (`runguard.status_row`): `{"recordType": "status", "status": "error", "message": "Invalid input, nothing was fetched or charged: <reason>", "errorType": "InvalidInput", "stage": "input", "error": "<reason>", "statusRow": true, "charged": false, "checkedAt": ...}` (pushed with no charged event), with the same message as the terminal status message. No connection is opened, no record is charged and no `RUN_SUMMARY` is saved. The prefix is "nothing was fetched or charged" (not "...and no results were charged") because this Actor's pricing has no start event; the reason texts are unchanged; the status message prefix changes from `Invalid input: ` to the house prefix above.
  - Wrong-type crashes (uncaught `TypeError`/`AttributeError`/`OverflowError`, exit 91) now give the same row with the reason `a field has the wrong type (<python error>).`
  - A whole input that is not a JSON object (a string, a number, a list of objects) used to crash (exit 91); it now gives the same row with the reason `the input must be a JSON object, got <type>.` An empty list or null input still means `{}`.
  - Unchanged: a bad SFTP connection setting (environment, not user input) still calls `Actor.fail`; server-down and other runtime failures keep the existing "Not charged" status row.
- **Memory:** `.actor/actor.json` `minMemoryMbytes` 256 -> 128 (`defaultMemoryMbytes` was already 256; max stays 1024; no `defaultRunOptions`). Local peak RSS is about 71 MB. The README had no memory or start-event text to change.
- `requirements.txt`: `apify >= 4.0.0, < 5.0.0` pinned to `apify >= 4.1, < 4.2`.
- Pricing, default input, prefill and `exampleRunInput` unchanged.
- Files: `my_actor/main.py` (new `end_invalid_input`, non-object check and wrong-type wrap around `parse_input`), `.actor/actor.json`, `README.md` (new FAQ entry), `requirements.txt`, `CHANGELOG.md`, `tests/test_invalid_input.py` (new).

### 2026-10-30, 10:15 PM EDT (actor.json version unchanged: 0.1), docs and tests only

- README.md (publisher, from Echo's README.proposed.md checked against the code): `maxResults` defaults to 5 and `{}`
  costs about $0.01; `onlyNewSinceLastRun` continues a file after the last charged record (no record charged twice);
  the old warning that an early stop charges the same records again is removed; new FAQ entries for the free status
  row when the state server fails and for the time budget (10 s per step, 2 attempts, nothing new after 40 s, hard
  stop at 50 s for `maxResults` 1-100; 30 s / 3 attempts and a 30 s margin before the run timeout otherwise).
- tests/test_default_limit.py: removed the xfail marker (and its "README says 100" note) from
  `test_readme_documents_default`; it now passes.

### 2026-10-30, later the same evening (actor.json version unchanged: 0.1)

#### Changed: default `maxResults` 10 -> 5

`{}` and the Console prefill now stop at 5 charged records, at most $0.01 with corporation records (Store tester
rule: about $0.01 or less). `0` still means no limit.

#### Fixed: the run failed (and could hang ~100 s) when the state SFTP server could not be reached

Any source failure (connection refused, accepted but silent, closed, login rejected, any other error, or the time
budget running out) now ends the run SUCCEEDED with one free status row: `recordType: "status"`, `status: "error"`,
`message` starting "Not charged", plus `errorType`, `stage`, `host`, `attempts`, `recordsDeliveredBeforeError`. The
row is pushed without a charged event. `RUN_SUMMARY` gets `error`, `connectAttempts` and `timeBudget`. Invalid input
still fails the run, as before.

Time budget (new `my_actor/runguard.py`, same rules as the other Actors' runguard):

- Demo runs (`maxResults` 1-100, so `{}` and the prefill): 10 s per connect/read step (TCP, SSH banner, handshake,
  login, SFTP reads), at most 2 attempts, at most 2 s between them, no new attempt or file after 40 s, and the
  whole source part is cancelled at 50 s.
- Larger runs (`maxResults` 0 or over 100): 30 s per step and 3 attempts as before, but no attempt or file is
  started later than 30 s before the platform run timeout (`ACTOR_TIMEOUT_AT`), and attempt timeouts and back-off
  waits are cut to end by then; the source part is cancelled 10 s after that point.
- A run stopped by the budget after delivering records keeps them (and, with `onlyNewSinceLastRun`, the offset), and
  reports `stopReason: "timeBudget"`.

#### Files changed

- `my_actor/runguard.py` (new): demo detection, deadlines, status row.
- `my_actor/sftp_client.py`: `connect()` takes `deadline` and `max_sleep`; the login timeout follows the step timeout.
- `my_actor/main.py`: source part wrapped in the time budget, free status row on any source error, no new file
  after the deadline.
- `my_actor/inputs.py`: `DEFAULT_MAX_RESULTS = 5`.
- `.actor/input_schema.json`: `maxResults` default and prefill 5, description "default 5, at most $0.01".
- `tests/test_fail_soft.py` (new): refused port, silent listener ({} and a large run near the platform timeout),
  rejected login, and unit tests of the budget logic.
- `tests/test_default_limit.py`: default 5; the README test (still xfail, README.md not edited) now checks only
  that README states the default of 5 and $0.01 and no older default.
- `tests/test_parser.py`: default 5.
- `CHANGELOG.md`.

### 2026-10-30 (actor.json version unchanged: 0.1)

#### Fixed: `onlyNewSinceLastRun` charged the same records again after an early stop

A run that stopped at `maxResults` or at the maximum charge did not mark the daily file as processed, so the next
run read the file from the top and charged the same first records again. The state record (`PROCESSED` in the named
key-value store `florida-sunbiz-daily-filings-state`) now also keeps, per file, the last line that was delivered.
The next run with `onlyNewSinceLastRun` continues after that line; a file is marked done only when it was read to the
end. Duplicate detection inside a file still works across the resume point.

- Write-ahead: the new offset is saved **before** each batch is pushed and charged. If the push raises, the previous
  offset is restored. If the run hits the charge limit mid-batch, the offset is set to the last charged record. A
  hard crash between the save and the charge can lose at most one batch (never charged); it can never charge a record
  twice.
- Backward compatible: state saved by older builds (`{"corporations": [...], ...}`) is read unchanged; the new data
  lives under `_version` / `_partial`, which older builds ignore.
- Runs without `onlyNewSinceLastRun` keep no state, as before.

#### Changed: default `maxResults` 100 -> 10

`{}` and the Console prefill now stop at 10 charged records (at most $0.02 with corporation records) instead of 100
($0.20). `0` still means no limit.

#### Files changed

- `my_actor/progress.py` (new): state v2 helpers (offsets, done files, trimming, v1 compatibility).
- `my_actor/main.py`: per-file resume, write-ahead offset saving, mark done only when fully read, run summary
  fields `resumedAfterLine` / `resumeAfterLine`, final status message.
- `my_actor/inputs.py`: `DEFAULT_MAX_RESULTS = 10`.
- `.actor/input_schema.json`: `maxResults` default and prefill 10; title and description of `onlyNewSinceLastRun`
  (was "so you never pay twice for the same file"; now describes the per-record resume) and description of `maxResults`.
- `tests/test_resume_state.py` (new): consecutive-run tests against the local SFTP fake, plus `progress.py` unit tests.
- `tests/test_default_limit.py`: expectations 100 -> 10; pricing info completed for apify SDK 4.2; README check
  marked xfail until README.md is updated.
- `tests/test_parser.py`: default `maxResults` expectation 10.
- `CHANGELOG.md` (new): this file.

README.md is NOT changed by this entry (needs the new default and the new `onlyNewSinceLastRun` behaviour).
