Skip to content

Releases: automators-com/flowproof

v0.22.0

Choose a tag to compare

@HappyDevs1 HappyDevs1 released this 07 Sep 07:40
68974ed

What's Changed

  • docs: close the flowproof config / README-adopting.md alignment gap by @HappyDevs1 in #547
  • chore(python): classify the package as alpha, not pre-alpha by @AminChirazi in #548
  • feat(trace): first-class side_effect record and lane (#465 Phase A, 1/5) by @AminChirazi in #468
  • feat(agent): record observed fs writes into the side-effect lane (#465 Phase A, 2/5) by @AminChirazi in #469
  • docs: plan 007, autonomous flow repair loop by @HappyDevs1 in #549
  • feat(agent,cli): implement plan 007's autonomous flow repair loop by @HappyDevs1 in #550
  • feat(agent,cli): recognize load-timing races as a distinct repair category by @HappyDevs1 in #551
  • feat(cli): give an engine-gap verdict one fresh retry before it's final by @HappyDevs1 in #552
  • feat(cli): notify on every command when a newer flowproof is available by @HappyDevs1 in #554
  • chore(release): bump version to 0.22.0 by @HappyDevs1 in #555

Full Changelog: v0.21.0...v0.22.0

v0.21.0

Choose a tag to compare

@HappyDevs1 HappyDevs1 released this 04 Sep 11:04

What's Changed

  • ci: run the Linux workflows on the self-hosted runners by @AminChirazi in #525
  • ci: use free hosted runners for public workflows by @AminChirazi in #526
  • ci: move workflows from self-hosted runners to GitHub-hosted runners by @HappyDevs1 in #530
  • ci: remove Operator PR package workflow by @AminChirazi in #533
  • feat(cli): flowproof config sap/fiori — plan 1, designed and built by @HappyDevs1 in #531
  • docs: add plan 3 — agent skill for flowproof config by @HappyDevs1 in #535
  • feat(cli,adapters): flowproof doctor --sap / --fiori by @HappyDevs1 in #532
  • Support shareable single-flow values files by @HappyDevs1 in #537
  • Plan Fiori field commit hardening by @HappyDevs1 in #538
  • feat: add AI authoring config by @HappyDevs1 in #544
  • docs: v1 compatibility, support and deprecation contract by @HappyDevs1 in #510
  • test(examples): verify a purchasing info record via OData directly by @HappyDevs1 in #517
  • docs: publish a security reporting policy and threat model by @HappyDevs1 in #506
  • docs: sync product docs with flowproof config, doctor, and AI authoring by @HappyDevs1 in #546

Full Changelog: v0.20.0...v0.21.0

v0.20.0

Choose a tag to compare

@AminChirazi AminChirazi released this 28 Aug 11:14
453259e

Added

  • A same-origin framed Click and Press … button now dispatch a real
    trusted click, not a refusal.
    A classic SAP GUI for HTML screen embeds
    its whole UI in a same-origin iframe, and every tab strip, button, and
    field on it lived behind that frame boundary. Clicking one was refused
    outright: a pointer action inside a frame could only ever reach it as an
    untrusted synthetic event, which an application checking isTrusted is
    free to ignore while the step still reports success - a real
    release-without-effect false green, so the refusal was the honest answer
    at the time. It no longer has to be. The driver now computes the target's
    page-absolute point (the frame's own offset in the parent document, plus
    the element's offset inside the frame) and dispatches the same
    Input.dispatchMouseEvent sequence the top-level document's click
    already used - the click is real, isTrusted is true, and an application
    driven this way cannot tell the difference from a person. Hover,
    Double-click, Right-click, and Upload inside a frame stay refused;
    none has this point-computation wired up yet.

  • A framed Type into a real <input>/<textarea> now types for real.
    Framed value-writes previously set .value through the native setter and
    fired synthetic input/change events - correct for anything listening
    to those events, and wrong for anything that is not. A live SAP GUI for
    HTML screen tracks its own field state off real keyboard events, the same
    way its desktop ancestor always did, and never saw a synthetic write at
    all: the field stayed visibly empty no matter what the trace said it
    typed. The driver now clicks the field for real, selects any existing
    value, and types the real keystrokes CDP would generate for an actual
    person - the same trusted path the top-level document's Type already
    used. Anything framed that is not a native input (a contenteditable, a
    custom widget) keeps the synthetic write, since there is no native
    keystroke target to land on.

  • A framed element is now found by the label its OWN form control
    resolves to, not by matching arbitrary text.
    the "Material" in the iframe "X" used to search the frame for any element whose raw
    textContent read exactly "Material" - which, on a screen where the
    visible label and the input it labels are separate DOM nodes (common
    outside hand-rolled markup), matched the label itself, never the field.
    Typing then landed on a <div>, silently no-op'd through the
    now-corrected trusted-keystroke path, and the search it was meant to fill
    in ran empty. Both the write and read paths now check a form control's
    native .labels association first - the same relationship scene()
    already uses to report a field's label back to authoring - before falling
    back to the old plain-text search, so a label naming a real field finds
    that field.

  • flowproof record shows the browser by default; --headed/--headless
    make the choice a flag, not tribal knowledge.
    Watching a web recording
    has been possible since FLOWPROOF_HEADED shipped, but only if you already
    knew the variable existed - nothing in --help said so, and a new user
    evaluating flowproof against a tool like Tosca (where you always watch the
    browser while recording) got a blank terminal by default. Recording is the
    one human-in-the-loop step, so it now opens Chromium unless told not to;
    flowproof run (replay) is unchanged and stays headless, because it is
    meant to run unattended on CI runners that often have no display at all,
    and a default flip there risks the false-green class of bug this project
    exists to catch - headed Chromium sizes its window from the desktop, so a
    visual baseline recorded one way and replayed the other reports a mismatch
    that has nothing to do with the flow. Anything that scripts flowproof record without a person watching (CI, a container, a cassette-generation
    pipeline) needs --headless now; everything else needs nothing at all.

  • The SAP test simulator models more than one screen shape. Every
    automated SAP check ran against the same flat VA01 window - one wnd[0],
    every field hanging straight off it. A fixture like that cannot fail.
    Window scoping could break, nesting could break, and nothing short of a
    person sitting at the one real SAP machine the team has would notice.

    The simulator now serves two more shapes. A classic GuiTableControl
    carries its cells as CHILDREN, under SAP's real [column,row] ids, so the
    tree walk has to recurse past depth two and FindById has to survive an id
    with brackets and a comma in it. Back opens a wnd[1] modal that sits in
    the session tree beside wnd[0] and leaves it again when dismissed - and
    the two windows deliberately share no text, so which window a reader is
    reading is a question with an answer.

    sap_sim_e2e drives both through the production COM engine.

Fixed

  • The Python SDK's dev/test lock resolved a known-vulnerable pytest for
    Python 3.9.
    pytest < 9.0.3 has insecure /tmp/pytest-of-{user}
    handling on UNIX (GHSA-6w46-j5rx-g56g); the fix only ever shipped in the
    9.x line, and 9.x dropped Python 3.9 support outright, so a plain floor
    bump was unsatisfiable for the 3.9 branch of the lock. The dev
    dependency group now declares its own requires-python = ">=3.10"
    ([tool.uv.dependency-groups]), separate from the package's own
    requires-python = ">=3.9" — running the test suite from source now needs
    3.10+, same as CI already effectively runs, while nothing changes for
    anyone installing the shipped flowproof package on 3.9. The lock now
    resolves one pytest version everywhere: 9.1.1, patched.

  • The nightly SAP suite was failing on the same field, every night, for a
    reason nobody had looked for.
    Every scheduled sap-e2e run since early
    August had failed on the first field of every flow that types into one —
    while the one flow that never touches a field kept passing. The heartbeat
    script written to keep an idle SAP session alive (#453) had nothing to do
    with it: it isn't invoked from the workflow at all, and even where it was
    separately registered on the runner, its scheduled task had no
    -Principal, so it silently stopped reaching the interactive desktop the
    moment that session's RDP connection went from active to disconnected.

    The actual failure was inside the job itself. sap-session-bootstrap.ps1
    logs the session in once, at job start; the login is genuinely fine at
    that point, confirmed by cargo test sap_e2e passing cleanly a few
    minutes later. But two full cargo builds follow with no shared
    incremental cache — around seven minutes of pure compilation, untouched
    by any SAP activity — and by the time the flow suite starts, the
    application server has logged the session out for sitting idle. The
    client raises its own "maximum user idle time exceeded" notice, a native
    Win32 dialog outside SAP's scripting object model — invisible to
    FindById, so nothing already in the driver could see or close it, and
    every field lookup after it just timed out waiting for a screen that was
    never going to render.

    sap-session-bootstrap.ps1 now recognizes that dialog and dismisses it
    (declining, not accepting — the default button opens a second window
    that would need dismissing too), and sap-e2e.yml runs bootstrap a
    second time immediately before the flow suite, not only once at job
    start, closing the actual idle window instead of one that had already
    passed.

v0.19.0

Choose a tag to compare

@AminChirazi AminChirazi released this 28 Aug 11:14
c11d1c1

Added

  • A refused value stays out of the trace. A recorded correction used to
    memorialize its own fumble: type 1200, type 800 (appended into 1200800 by
    the pre-0.18 typing), clear, type 500 - four steps where one is the truth,
    and every replay re-performed the lot, driving the app through its error
    state on purpose, for ever.

    The repo already had the principle, applied everywhere except here. An
    occluded click is not recorded, because it records a success the page never
    saw. A walk-away from a rejected form is not recorded, because it records a
    success the page refused to give. But a VALUE the page rejected - same
    verdict, from the same page - was recorded, and then replayed.

    When the rejected-form guard fires and the correction sticks, the writes
    that produced the red fields are now dropped before the trace is minted and
    the survivors renumbered. The trace reads as the one line a person would
    have left: the value that stuck. The live run is unchanged; only the
    fumble's memorial goes.

    Three guards keep the drop honest. The recording spans join BEFORE the
    prune, so heal's before/after frames keep pointing at the moment they
    filmed. An observer between fumble and correction - a capture, an assert, a
    click - blocks the prune, because it read the page WITH the fumble in it
    and a trace without it would replay a different observation than the one
    recording verified. And positional selector paths are never pruned, since
    the same string can name different elements across a DOM mutation.
    Everything unprovable fails open to the journal.

v0.18.0

Choose a tag to compare

@AminChirazi AminChirazi released this 28 Aug 11:14
0f4537a

Changed

  • Targeted typing means fill, on every adapter. Typing into a NAMED
    field now states what the field should read: whatever it held is replaced,
    the way every mainstream tool works. Appending was never a designed
    contract - it was an accident of the web keystroke path, and not even a
    uniform one, since framed typing, the select branch and SAP fields always
    replaced.

    What the accident cost was found the expensive way. A correction typed
    800 - the right value - into a payload field still holding the refused
    9000, and the page saw 9000800. The recording survived only because the
    model noticed the tool's own behaviour in the scene, cleared the field and
    retyped.

    Each adapter keeps the contract in its own idiom: web selects the content
    and sends the same trusted keystrokes (a keydown-filtering app still sees
    real keys), Windows UIA selects-all with its own chord and gains the
    clear_text the Replace grammar always erred on there, vision composes
    the chord its clear already used, SAP was already fill. Focused typing -
    Type <text> with no target - stays raw keystrokes and APPENDS: it is
    the step for dropdown filters, pre-focused rename boxes, and adding to
    what is there.

    A spec that relied on targeted typing appending would change meaning. No
    committed flow does, and the docs never promised it - but that is the
    reason this is 0.18.0 and not 0.17.1.

v0.17.0

Choose a tag to compare

@AminChirazi AminChirazi released this 28 Aug 11:14
f386c6b

Added

  • A refused value re-authors the rest of its step. A person handed "fill
    out all the vehicle data" types a truck-plausible payload, watches the form
    go red, reads "must be a number between 1 and 1000", types a smaller number
    and carries on. flowproof could not, and a recording died on a form a person
    would have finished.

    Two things stopped it, and the spec was not one of them.

    The guard that catches a rejected form before the click that leaves keyed
    its one-correction-per-problem rule on the FIELD, so a second attempt read
    as a repeat of the first however different the value — one try, where a
    person gets as many as it takes. The value was in the scene and unused. It
    is in the key now: a different value earns another look, the same value
    twice still reads as a decision.

    And nothing weighed the rule at the moment of choosing. A value outside a
    plainly stated range is now CHALLENGED before a keystroke reaches the
    browser, which is cheaper than a rejected form and leaves the guard's
    correction for a problem it has not already been warned about.

    Challenged, not refused. The rule is scraped from copy written for a person:
    "between 3 and 20 characters" is the same six words as a value range, a
    wrapper can lend one field's message to its neighbour, and a step may MEAN
    to enter something the page refuses to see what it says. A model that
    repeats its value after being asked is taken at its word, and the
    application decides. Being wrong costs a question, never a recording.

    Recorded against an unchanged spec, the trace now reads 5000, then 800,
    then 500 — the sequence a person would have produced.

v0.16.1

Choose a tag to compare

@AminChirazi AminChirazi released this 28 Aug 11:14
e7b6f90

Fixed

  • One rejected field was reported as three, two of them unfixable. A
    validation framework sets its marker on the WRAPPER, not the control, so
    the check for it walks up from the element. Asked of every scene entry,
    that flagged the label and the error message inside the wrapper as well as
    the field: one bad value named three times, two of them things nothing can
    type into.

    A recording died on it. The page refused a payload, the guard caught it
    before the click that leaves the screen and re-authored the step quoting
    the rule back — and the correction it asked for listed #payload beside a
    label.main and a span.error carrying the same sentence. A model gets
    one correction before the same rejection is treated as a decision rather
    than a hiccup, and that one can be spent on a span.

    Only a control can be rejected, and only a control can be corrected, so
    only a control is asked. The rule the page states is read the same way: a
    label repeating a constraint is not a field that has one.

v0.16.0

Choose a tag to compare

@AminChirazi AminChirazi released this 28 Aug 11:14
6c3eef3

Recording could not finish a flow written the way a tester talks. This
release is the seven-defect chain that stopped it, found by recording one
insurance quote against a live application until it worked.

Every one of them was a fault in what the recorder could PERCEIVE, and
every one of them read, at first, as the authoring model being wrong. It
never was. The scene described a page that had moved on, or omitted a
control the page had drawn for itself, or reported a rejected form as a
clean one — and the model reasoned correctly from what it was handed.

The flow this was found on now records end to end and replays
deterministically, six consecutive runs, zero model calls.

Changed

  • A version tag now publishes. Both publish workflows were
    workflow_dispatch only, so pushing v0.15.0 did nothing and the release
    still had to be triggered by hand — a step easy to forget after tagging, and
    easy to mistake for having released.

    They now also fire on a tag matching v[0-9]+.[0-9]+.[0-9]+. The pattern is
    exact rather than v* so an experimental tag cannot ship a release, and a
    new guard refuses a tag whose name disagrees with the version it would
    build. That guard is not hypothetical: a v0.14.0 tag was created on the
    commit that bumps to 0.15.0. While nothing consumed tags it was merely wrong
    in the history; with tags publishing, the same slip would ship 0.15.0
    labelled 0.14.0, and registry versions are immutable.

Fixed

  • The authoring scene could describe a page that no longer existed, and the
    model took the blame.
    The inventory was read the instant the previous step
    finished. A step that navigates leaves the next step's scene racing the new
    page, and a server that renders every variant of a form and hides the
    irrelevant ones in script briefly presents all of them as rendered.

    On the Tricentis sample app this was reliable rather than rare. Open the truck insurance quote was authored correctly, and the scene captured
    immediately after was the union of two forms: a motorcycle #model
    actionable, with its own options list — beside the truck's #payload. The
    model grounded faithfully onto #model, exactly as instructed, and the
    recorder then refused the step because no such element existed by the time
    it ran. The failure read as a model inventing a selector. It was the scene
    that lied; the model had copied a target it was entitled to copy.

    The web scene is now read only once two readings agree on which targets
    exist, with the document loaded. That narrows the window rather than
    closing it — a navigation that has not begun still presents a settled old
    page, and a single-page app never leaves readyState complete at all — but
    it removes the case this was losing recordings to.
    Agreement before load completes does not count, because that is precisely
    the window the pre-script union looks stable in. Only the set of targets has
    to hold still — a clock, a character counter, or a field mid-edit changes
    text on every reading without changing which elements exist, and waiting on
    those would wait forever. A page that never goes quiet is captured anyway
    after a bounded wait: recording a carousel is better than hanging on one.

  • Recording refused a click replay would have waited for. The occlusion
    probe's own comment said it used "the same predicate as replay's
    actionability gate, so the two agree by construction". The predicate was the
    same. The waiting was not: replay polls the gate to the step's deadline,
    recording asked once and aborted. A toast on its way out, a modal backdrop
    mid-fade, a carousel between slides — each reports "something else would
    receive this click" the instant it is asked, and each is gone a moment
    later. Recording refused pages it could have recorded, and the trace it
    declined to write would have replayed.

    Recording now polls the same gate for the same budget, measured as wall
    clock exactly as replay measures it — a count of polls would omit the probe
    round-trips between them and let recording out-wait replay on a slow
    transport. Not a new constant:
    recording bakes step_timeout_ms() into every targeted step's existence
    precondition and replay waits exactly that recorded value, so both are
    derived from the one number and follow its FLOWPROOF_STEP_TIMEOUT_MS
    override together. The direction is the part that matters — recording must
    never accept a click replay would reject, so out-waiting replay is the one
    error that cannot be tolerated: it mints a trace whose first replay fails.

    An occluder that outlasts the budget is still refused, with the message it
    always had. This buys the transient cases only, which is all a wait can buy.

  • The two E2E jobs had been red on main for a day, and nothing on a pull
    request could see it.
    web E2E and windows build + E2E are off the PR
    path, so they run only on push to main — every PR involved went green, and
    main went red on merge, repeatedly, for two unrelated reasons.

    notepad_author_e2e builds a FlowSpec literally and the struct had grown
    three fields it did not list (apps, exports, login). The file is
    #![cfg(windows)], so no amount of cargo clippy --all-targets on Linux or
    macOS ever compiled it.

    web_e2e asserted the whole-run GIF renders, but GIF assembly became opt-in
    when --video landed and the test kept running with defaults — it expected
    an artifact nothing had been told to produce. It now asks for one, so the
    assertion tests what it claims to.

v0.15.0

Choose a tag to compare

@AminChirazi AminChirazi released this 06 Aug 11:48
37c8de1

What's Changed

  • fix(agent): leave room for Anthropic reasoning by @AminChirazi in #395
  • Fix SAP Logon startup, login, and transaction navigation by @AminChirazi in #396
  • docs: add Tricentis insurance example by @AminChirazi in #398
  • Add visible browser inspection mode by @AminChirazi in #397
  • Make visual recording configurable and video opt-in by @AminChirazi in #399
  • docs: record the visual-recording default change by @AminChirazi in #400
  • feat(python): expose the recording controls on the SDK and MCP tools by @AminChirazi in #402
  • chore(release): prepare 0.13.0 by @AminChirazi in #401
  • docs: the recording page opened with the design, not the controls by @AminChirazi in #404
  • ci: a deploy that never happened reported success by @AminChirazi in #405
  • docs: three flags shipped that no page mentioned by @AminChirazi in #403
  • fix(gate): protection.json would have removed the constitution checks by @AminChirazi in #406
  • feat(agent): flows declare exports: — values handed to the flows after them by @AminChirazi in #407
  • fix(agent): a step naming a whole form authored one field by @AminChirazi in #409
  • feat(cli): a suite run threads a passing flow's exports to the flows after it by @AminChirazi in #410
  • perf(adapters): answer existence and actionability probes in one CDP round trip by @AminChirazi in #412
  • fix(replay): render each recording frame under exactly one step by @AminChirazi in #411
  • feat(agent): the multi-surface vocabulary — apps: and in: blocks parse and validate by @AminChirazi in #413
  • feat(agent): surface semantics — which kinds may be a surface, and where config sits by @AminChirazi in #415
  • feat(trace): a step can say which surface ran it — apps map + surface attribution by @AminChirazi in #414
  • feat(driver): a SurfaceRegistry holds several surfaces and drives exactly one by @AminChirazi in #417
  • feat(agent): a multi-surface flow records — surfaces activate, captures cross blocks by @AminChirazi in #418
  • feat(replay): a multi-surface trace replays — each step on its recorded surface by @AminChirazi in #419
  • feat(trace): a web surface carries its own browser: config by @AminChirazi in #420
  • feat(agent): a desktop surface carries its own window: — and vision becomes a surface by @AminChirazi in #421
  • feat(agent): a screenshot baseline names its surface — assert_screenshot joins multi-surface flows by @AminChirazi in #426
  • feat(agent): heal works on multi-surface flows — Phase 2's list is empty by @AminChirazi in #429
  • docs: Phase 3 design — the in-flow agent seam by @AminChirazi in #430
  • chore(release): prepare 0.14.0 by @AminChirazi in #416
  • fix(python): upgrade PyO3 to 0.29, clearing two RustSec advisories by @AminChirazi in #431
  • fix(sdk): upgrade cryptography past GHSA-g6cj-pr64-35w5 by @AminChirazi in #434
  • test(agent): read the docs, so a refused step cannot be documented again by @AminChirazi in #435
  • A whole-form step could author nothing, and a keystroke cost 213ms by @AminChirazi in #437
  • chore(release): prepare 0.15.0 by @AminChirazi in #439

Full Changelog: v0.12.2...v0.15.0

FlowProof 0.12.2

Choose a tag to compare

@AminChirazi AminChirazi released this 04 Aug 06:38
662303c

FlowProof 0.12.2

This release makes natural-language authoring substantially more capable while keeping recorded replay deterministic and model-free.

Highlights

  • Author ordinary human instructions for clicking, typing, scrolling, keyboard input, drag-and-drop, single and multi-selects, counting, captured-value reuse, tables, and same-origin iframe content.
  • Ground model output against the live screen inventory, including scoped and opaque scene tokens, instead of requiring users to write CSS or element IDs.
  • Close headed Chrome sessions cleanly after execution, avoiding orphan windows, empty tabs, and accumulating browser processes.
  • Improve Windows CI coverage for containment, native UI Automation authoring/replay, and Windows Server 2025 runner behavior.

Documentation

The release assets include refreshed training and solutions PDFs written with human-language FlowProof steps for version 0.12.2.

See CHANGELOG.md for the detailed change list.