Skip to content

fix(flow): keep record identifiers in an API step's URLs as run parameters - #1369

Merged
abrichr merged 1 commit into
mainfrom
fix/api-proposal-url-identifiers
Oct 19, 2026
Merged

abrichr merged 1 commit into
mainfrom
fix/api-proposal-url-identifiers

Conversation

@abrichr

@abrichr abrichr commented Oct 19, 2026

Copy link
Copy Markdown
Member

Third of six fixes from an external review of #1269. Stacked on #1368, because both change api_proposal.py; it retargets to main when that merges.

Bug

A save to /save.php?record_id=17 compiled to /save.php, losing the query. /new.php?record_id=17 stayed literal in the form-fetch URL. A later run therefore read record 17's form and posted without the identifier.

Fix

  • The save URL and the form page URL keep their query.
  • A path segment or query value equal to exactly one input's demonstration value becomes that parameter (/app/save.php?record_id={record_id}&mode=edit). The rest is kept as recorded. A value equal to two inputs is refused as ambiguous.
  • A hidden form field echoing a URL-bound identifier (an update form's pid) binds to the same parameter. Without this, fix(flow): bind API step fields by which step typed them, not by equal values #1368's provenance rule would refuse it, because no step typed it.
  • Origin: the form page, the pre-checks and the save must share one origin, so an absolute action to another host is refused. Before this PR it was silently replayed against the page's own origin.
  • The actuator fills both URLs with the run's values, URL-encoded (fill_url: a path segment quoted whole, query values form-encoded).
  • API_TIER.md is updated.

Tests

tests/test_api_proposal_url_identifiers.py (new, 6 tests):

  • the reviewer's case binds record_id in both URLs, and a later run with record_id=42 reads /app/new.php?record_id=42 and posts to /app/save.php?record_id=42;
  • an ambiguous URL value is refused;
  • a cross-origin save is refused;
  • a hidden pid echoing the URL id binds as {record_id};
  • fill_url encodes values and keeps literals.

47 pass with the existing API-tier and provenance tests. On the real OpenEMR referral recording the API step is still proposed; its URLs carry no identifiers, so they are unchanged.

Synthetic values only.

🤖 Generated with Claude Code

…eters

Review of #1269: a save to /save.php?record_id=17 compiled to /save.php,
losing the query, while /new.php?record_id=17 stayed literal in the
form-fetch URL, so a later run read and wrote record 17.

The save URL and the form page URL keep their query. A path segment or
query value equal to exactly one input's demonstration value becomes that
parameter; the rest is kept as recorded; a value equal to two inputs is
refused as ambiguous. A hidden field echoing a URL-bound identifier (an
update form's pid) binds to the same parameter. The form page, pre-checks
and save must share one origin. The actuator fills the URLs with the run's
values, URL-encoded (fill_url).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from fix/api-proposal-bind-by-provenance to main October 19, 2026 22:22
@abrichr
abrichr merged commit c025ff1 into main Oct 19, 2026
35 checks passed
@abrichr
abrichr deleted the fix/api-proposal-url-identifiers branch October 19, 2026 22:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant