Evidence
The measured numbers behind the write-up, with the files they come from. All runs used Oracle Forms and Infrastructure 14.1.2 under Oracle's trial licence on one evaluation host, Oracle Database Free, and the Forms Standalone Launcher on a virtual display. The migrated apps ran on .NET 10 and PostgreSQL.
What database and API checks missed
An independent evaluator checked a .NET/PostgreSQL rebuild of the retail app, written by an ordinary coding agent, through its database and its API: it passed four of five finding groups (13 of 16 SQL observations, 7 of 10 API checks). The first recorded scenario (create an order with two lines, change a quantity, save) replayed against the same build found 9 defects in 71 values, none of them among the evaluator's findings:
- No order ID on open. Forms draws ORD_SEQ on opening, shows 16 and saves 17; the target shows nothing until save and saves 16. (11 values)
- ORDER_DATA saved with the clock time. Forms stores NULL; the target lets the column default apply. (4 values)
- ITEM_ID cannot be entered; the target draws it from item_seq. Stored values agree only because the fresh sequence starts at 1. (26 values)
- Line total computed while typing, not when the quantity is validated. (5 values)
- Adding a line blanks the total of the earlier lines. (3 values)
- Save shows no alerts. Forms shows FRM-40400, then the trigger's success message. (10 values)
- Status line shows 'No records...' from opening until save; Forms' message line is empty. (10 values)
- Focus after opening stays on the menu button; Forms puts it on CUSTOMER_ID. (1 values)
- Tab from QUANTITY goes to UNIT_PRICE; in Forms it goes to ITEM_ID. (1 values)
Further ORDERS scenarios found data-level faults in the same build: it overwrote an existing order instead of inserting a new one, saved an order Forms refuses (a required field empty), and updated a line where Forms rejects a duplicate key.
Retail: coverage
| Module | Triggers | Program units | Covered | Unreachable | Blocked |
|---|---|---|---|---|---|
| MAIN_MENU | 7 | 0 | 7 | 0 | 0 |
| ORDERS | 14 | 3 | 17 | 0 | 0 |
| CATEGORIES_FORM | 9 | 0 | 9 | 0 | 0 |
| CUSTOMERS_FORM | 9 | 0 | 9 | 0 | 0 |
| EMPLOYEES_FORM | 9 | 0 | 9 | 0 | 0 |
| PRODUCT_FORM | 9 | 0 | 9 | 0 | 0 |
| total | 57 | 3 | 60 | 0 | 0 |
Decision branches: 18, of which 14 covered, 2 unreachable (retirement candidates) and 2 blocked. Database PL/SQL: 1 unit(s) in the coverage report, 1 of 1 blocks covered. Exit check: PASS.
Every branch with its class
| Trigger or unit | Branch | Class | Scenarios or reason |
|---|---|---|---|
ORDERS/ORDERS.PUSH_BUTTON10/WHEN-BUTTON-PRESSED | commit succeeded (FORM_SUCCESS TRUE) | covered | order-two-lines |
ORDERS/ORDERS.PUSH_BUTTON10/WHEN-BUTTON-PRESSED | commit failed (FORM_SUCCESS FALSE) | covered | orders-add-new, orders-save-fails |
ORDERS/ORDERS/ON-CHECK-DELETE-MASTER | detail rows exist, delete refused | covered | orders-delete-refused |
ORDERS/ORDERS/ON-CHECK-DELETE-MASTER | no detail rows, delete goes ahead | covered | orders-delete-empty |
ORDERS/ORDERS/ON-POPULATE-DETAILS | master record NEW or INSERT (or ORDER_ID null) - no detail query | covered | orders-add-new |
ORDERS/ORDERS/ON-POPULATE-DETAILS | detail queried | covered | orders-add-new, orders-browse, orders-delete-empty, orders-delete-refused, orders-navigate-unsaved |
ORDERS/ORDERS/ON-POPULATE-DETAILS | cursor moved by the query, returned to the start item | covered | orders-add-new, orders-browse, orders-delete-empty, orders-delete-refused, orders-navigate-unsaved |
ORDERS/ORDERS/ON-POPULATE-DETAILS | cursor still on the start item after the query | unreachable | The relation is not deferred, so QUERY_MASTER_DETAILS always does GO_BLOCK('ORDER_ITEMS') and the cursor always leaves the master item it started on. |
ORDERS/unit:QUERY_MASTER_DETAILS | relation not deferred - GO_BLOCK and EXECUTE_QUERY | covered | orders-add-new, orders-browse, orders-delete-empty, orders-delete-refused, orders-navigate-unsaved |
ORDERS/unit:QUERY_MASTER_DETAILS | relation deferred - SET_BLOCK_PROPERTY non-coordinated | unreachable | The only relation (ORDERS_ORDER_ITEMS) is not deferred and nothing changes that at run time. |
ORDERS/unit:QUERY_MASTER_DETAILS | GO_BLOCK to the detail failed - exception handler | blocked | Needs GO_BLOCK('ORDER_ITEMS') to fail during master coordination. The block has no PRE-BLOCK, WHEN-NEW-BLOCK-INSTANCE or record-level validation that can refuse entry, and the master record was validated before coordination began; no input found that makes it fail. FRD would show it as CHECK_PACKAGE_FAILURE with FORM_SUCCESS FALSE. |
ORDERS/unit:CHECK_PACKAGE_FAILURE | previous built-in succeeded | covered | orders-add-new, orders-browse, orders-delete-empty, orders-delete-refused, orders-navigate-unsaved |
ORDERS/unit:CHECK_PACKAGE_FAILURE | previous built-in failed - raise FORM_TRIGGER_FAILURE | blocked | Called after GO_BLOCK and GO_ITEM in the relation code. Needs one of those navigations to fail, the same condition as the QUERY_MASTER_DETAILS exception branch; no input found. |
ORDERS/unit:CLEAR_ALL_MASTER_DETAILS | detail block NEW - nothing to clear | covered | orders-add-new, orders-browse, orders-delete-empty, orders-delete-refused, orders-navigate-unsaved |
ORDERS/unit:CLEAR_ALL_MASTER_DETAILS | detail block queried - CLEAR_BLOCK(NO_VALIDATE), cursor returned | covered | orders-add-new, orders-browse, orders-delete-empty |
ORDERS/unit:CLEAR_ALL_MASTER_DETAILS | changed block below the master found (First_Changed_Block_Below) | covered | orders-navigate-unsaved |
ORDERS/unit:CLEAR_ALL_MASTER_DETAILS | changes - CLEAR_BLOCK(ASK_COMMIT), user discards or saves, detail cleared | covered | orders-navigate-unsaved |
ORDERS/unit:CLEAR_ALL_MASTER_DETAILS | changes - CLEAR_BLOCK(ASK_COMMIT), user cancels, FORM_TRIGGER_FAILURE, cursor returned | covered | orders-navigate-unsaved |
Database PL/SQL units
| Unit | Type | Blocks | Covered | Class |
|---|---|---|---|---|
| TRG_CALC_ITEM_TOTAL | TRIGGER | 1 | 1 | covered |
Report: retail-coverage.md.
HR: coverage
| Module | Triggers | Program units | Covered | Unreachable | Blocked |
|---|---|---|---|---|---|
| CONSOLE | 5 | 0 | 5 | 0 | 0 |
| EMPLOYEES | 14 | 3 | 17 | 0 | 0 |
| LOGIN | 2 | 0 | 2 | 0 | 0 |
| total | 21 | 3 | 24 | 0 | 0 |
Decision branches: 32, of which 24 covered, 4 unreachable (retirement candidates) and 4 blocked. Database PL/SQL: 11 unit(s) in the coverage report, 13 of 13 blocks covered. Exit check: PASS.
Every branch with its class
| Trigger or unit | Branch | Class | Scenarios or reason |
|---|---|---|---|
LOGIN/LOGIN.LOGIN/WHEN-BUTTON-PRESSED | user and password match - console opens | covered | console-apex, console-logout, employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search, login-ok |
LOGIN/LOGIN.LOGIN/WHEN-BUTTON-PRESSED | no matching user - ALERT_FAILURE | covered | login-fail |
CONSOLE/CONSOLE.EMPLOYEES/WHEN-BUTTON-PRESSED | EMPLOYEES not open - OPEN_FORM | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search |
CONSOLE/CONSOLE.EMPLOYEES/WHEN-BUTTON-PRESSED | EMPLOYEES already open - GO_FORM | blocked | Needs the CONSOLE button pressed while EMPLOYEES is open. OPEN_FORM(ACTIVATE) puts the EMPLOYEES window over the console, and the way back is the FSAL "Window" menu, which is a client menu with no Forms name (LWMenu1); the runner only clicks named elements. Harness limit. |
EMPLOYEES/BUTTONS.SAVE_BUTTON/WHEN-BUTTON-PRESSED | block changed - commit | covered | employees-change-department, employees-change-job, employees-create |
EMPLOYEES/BUTTONS.SAVE_BUTTON/WHEN-BUTTON-PRESSED | nothing changed - no commit | covered | employees-details |
EMPLOYEES/BUTTONS.DELETE_BUTTON/WHEN-BUTTON-PRESSED | confirmed - delete and commit | covered | employees-delete |
EMPLOYEES/BUTTONS.DELETE_BUTTON/WHEN-BUTTON-PRESSED | cancelled - nothing deleted | covered | employees-delete-refused |
EMPLOYEES/BUTTONS.CLEAR_BUTTON/WHEN-BUTTON-PRESSED | changed, confirmed - changes discarded and requeried | covered | employees-clear |
EMPLOYEES/BUTTONS.CLEAR_BUTTON/WHEN-BUTTON-PRESSED | changed, cancelled - changes kept | covered | employees-clear |
EMPLOYEES/BUTTONS.CLEAR_BUTTON/WHEN-BUTTON-PRESSED | nothing changed - no question | covered | employees-clear |
EMPLOYEES/EMPLOYEES/ON-CHECK-DELETE-MASTER | job history exists, delete refused | covered | employees-delete-refused |
EMPLOYEES/EMPLOYEES/ON-CHECK-DELETE-MASTER | no job history, delete goes ahead | covered | employees-delete |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | master record NEW or INSERT - no detail query | covered | employees-clear, employees-create |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | DEPARTMENT_ID set - department queried | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | DEPARTMENT_ID null - no department query | covered | employees-search |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | MANAGER_ID set - manager queried | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-delete, employees-delete-refused, employees-details, employees-search |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | MANAGER_ID null - no manager query | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | EMPLOYEE_ID set - history queried | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | EMPLOYEE_ID null on a queried record - no history query | unreachable | EMPLOYEE_ID is the primary key of EMPLOYEES, so a queried record always has it; NEW and INSERT records return before this point. |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | cursor moved by the queries, returned to the start item | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search |
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILS | cursor still on the start item after the queries | unreachable | Every relation is not deferred, so each detail query does GO_BLOCK to the detail and the cursor always leaves the master item; HISTORY is always queried (EMPLOYEE_ID is the key). |
EMPLOYEES/unit:QUERY_MASTER_DETAILS | relation not deferred - GO_BLOCK and EXECUTE_QUERY | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search |
EMPLOYEES/unit:QUERY_MASTER_DETAILS | relation deferred - SET_BLOCK_PROPERTY non-coordinated | unreachable | No relation of EMPLOYEES is deferred and nothing changes that at run time. |
EMPLOYEES/unit:QUERY_MASTER_DETAILS | GO_BLOCK to the detail failed - exception handler | blocked | Needs GO_BLOCK to a detail block to fail during coordination. The detail blocks have no PRE-BLOCK, WHEN-NEW-BLOCK-INSTANCE or validation that can refuse entry; no input found that makes it fail. |
EMPLOYEES/unit:CHECK_PACKAGE_FAILURE | previous built-in succeeded | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search |
EMPLOYEES/unit:CHECK_PACKAGE_FAILURE | previous built-in failed - raise FORM_TRIGGER_FAILURE | blocked | Called after GO_BLOCK and GO_ITEM in the coordination units; no input found that makes these fail (see the QUERY_MASTER_DETAILS exception branch). |
EMPLOYEES/unit:CLEAR_ALL_MASTER_DETAILS | a detail block holds rows - cleared without validation | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-search |
EMPLOYEES/unit:CLEAR_ALL_MASTER_DETAILS | every detail block empty - nothing cleared | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search |
EMPLOYEES/unit:CLEAR_ALL_MASTER_DETAILS | cursor moved by the clearing, returned to the start item | covered | employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-search |
EMPLOYEES/unit:CLEAR_ALL_MASTER_DETAILS | changed detail block below the master - ask to commit | unreachable | DEPARTMENT, MANAGER and HISTORY allow no insert, update or delete, so a detail block is never CHANGED. |
EMPLOYEES/unit:CLEAR_ALL_MASTER_DETAILS | a detail block not cleared - raise FORM_TRIGGER_FAILURE | blocked | Needs CLEAR_BLOCK(NO_VALIDATE) on a read-only detail block to leave rows behind; no input found that makes it fail. |
Database PL/SQL units
| Unit | Type | Blocks | Covered | Class |
|---|---|---|---|---|
| COMPOSE_URL | FUNCTION | 3 | 3 | covered |
| GENERATE_TOKEN | FUNCTION | 5 | 5 | covered |
| VALIDATE_TOKEN | FUNCTION | None | 0 | unreachable |
| ADD_JOB_HISTORY | PROCEDURE | 1 | 1 | covered |
| INSERT_LOG | PROCEDURE | None | 0 | unreachable |
| SECURE_DML | PROCEDURE | None | 0 | unreachable |
| APEX_TRG_01 | TRIGGER | None | 0 | unreachable |
| REQUESTS_TRG_01 | TRIGGER | None | 0 | unreachable |
| SECURE_EMPLOYEES | TRIGGER | None | 0 | unreachable |
| UPDATE_JOB_HISTORY | TRIGGER | 4 | 4 | covered |
| USERS_TRG_01 | TRIGGER | None | 0 | unreachable |
Report: hr-coverage.md. Of the schema's 11 PL/SQL units, the four the Forms app runs are fully covered (13 of 13 blocks); the other seven cannot run from Forms (a policy function that ships disabled, its helper, the APEX side and audit triggers on tables the app never writes).
Repeatability
Every golden record was recorded three times from the restored baseline, and the three were byte-identical for all 17 retail and all 17 HR scenarios: retail, HR. Clock and run-date values are masked; the masks were checked by recording on another day (17 of 17 retail records equal) and with the Forms runtime's clock moved ten days ahead (one scenario per app). Every replay against a migrated app was also run three times from a fresh database, with byte-identical diffs: retail, HR.
Retail: from first conversion to parity
| Round | Scenarios at parity | Defect values | Defect groups | Target change | Elapsed |
|---|---|---|---|---|---|
| 0 (as converted) | 1 of 17 | 403 | 164 | - | replay 950 s |
| 1 | 17 of 17 | 0 | 0 | 4 files, +414 / -205 lines | repair 19 min, replay 896 s |
The 164 defect groups came down to twelve findings, among them: the main menu's start-up state, keys typed by the user instead of drawn from sequences, record navigation and its messages, Add, Save and Delete semantics, date display, ORDERS opening on a new record with a drawn order number, the three-button save question, the refused delete of an order with lines, and the messages of a failed save. All were repaired in one round by an agent that had the diffs and golden records but not the Forms source. Files: round 0, round 1.
HR: from first conversion to parity
The HR app was converted to .NET 10 and PostgreSQL from its Forms source by one coding agent in about 20 minutes (12 files, 1809 lines), without access to the scenarios or golden records.
| Round | Scenarios clean | Defect values | Target change | Elapsed |
|---|---|---|---|---|
| 0 (as converted) | 4 of 17 | 73 in 13 scenarios | - | replay 5 min 04 s |
| 1 | 17 of 17 | 0 (52 repaired, 21 waived) | 3 files, +37 / -21 lines | repair about 4 min, replay 5 min 18 s |
Round 0 defects by kind: the record indicator (40 values), focus after alerts and tab clicks (6), the message line (4), the generated employee id (2), the APEX links (13, waiver H1) and a stale department number (8, waiver H2). Files: round 0 summary, round 0 defects, round 1 summary, round 1 repair diff, final three runs. The APEX token's signing key was later moved from the source's literal to configuration; the two scenarios that use it give byte-identical diffs (re-run).
Onboarding a new application
The HR app was the first application the harness had not been built on. One agent took 2 h 39 min from receiving the source to a passing coverage report:
| Phase | Elapsed |
|---|---|
| Find and pin the application, check its licence | 9 min |
| Host setup: schema, APEX runtime, compile, Forms Services sections | 9 min |
| Application profile and name-locator checks on three modules | 10 min |
| 17 scenarios, branch classification and generic harness adjustments | 1 h 32 min |
| First coverage loop with a passing exit check | 48 min |
The full loop (recording pass, diagnostics replay, three runs) takes 169 s per scenario on average.
Limits of this evidence
- Only the Forms Standalone Launcher on Forms 14.1.2 was tested; Forms 12.2.1.x, the Java plugin and Web Start are untested.
- Both applications are small public samples (6 and 3 modules, 60 and 24 triggers and units).
- Parity covers the 17 recorded scenarios of each app and the items the mapping names. Behaviour no scenario exercises is unverified.
- One target adapter exists (a web app in a browser, PostgreSQL). Files, reports, mail and HTTP calls are not recorded yet.
- Tolerance policies and waivers were drafted by the agent; agreeing them with a real customer is untested.