T07: agentic realization over a stdlib browser surface

Two decisions taken with the operator: stdlib HTML driver instead of
Playwright (F-0004), and a deterministic discovery runtime instead of a live
model. Both sit behind interfaces so the alternatives drop in later.

- html.py: stdlib DOM parse and query
- agentic.py: DiscoveryRuntime (agentic arm, ignores data-td by construction)
  and RecordedSelectorRuntime (control arm, uses the strongest identifier the
  page offers)
- browser.py: per-actor sessions over real HTTP, constructed per call so no
  actor inherits another's connection state
- cost/nondeterminism metrics recorded from the first run

F-0005 (CONCEPT_DRIFT): the H-001 result is a narrowing. Where test ids are
preserved, discovery 9/9 and recorded selectors 9/9 - the semantic action buys
nothing. Where they are dropped, discovery 2/3 and recorded 0/3. The concept
model presents semantic actions as generally superior; the evidence says
conditionally superior.

M21 and M22 added mid-task: the deciding side of the axis was N=1. M22 (field
names renamed) defeats the heuristic and is the first concrete evidence that a
live model would add capability, not just cost.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1629012@bnt-lap001
Assistant-Session: 78d4fb13-8a1e-474b-87a3-9b9261c49a39
This commit is contained in:
tegwick 2026-08-22 23:50:29 +02:00
parent 925ff2dd91
commit 44faf3de8e
23 changed files with 1008 additions and 9 deletions

View file

@ -50,9 +50,10 @@ def _render(app: LabApp, user_id: str, resource_id: str) -> str:
role = ' role="button"' if tag == "a" else ""
href = ' href="#"' if tag == "a" else ""
subject_name = "recipient" if app.ui_field_names == "renamed" else "subject_id"
subject_field = (
'<label for="subject">Person</label>'
'<input id="subject" name="subject_id" data-td="subject">'
f'<input id="subject" name="{subject_name}" data-td="subject">'
)
permission_field = (
'<label for="permission">Permission</label>'
@ -188,6 +189,9 @@ class LabHandler(BaseHTTPRequestHandler):
self._send(404, {"error": "not found"})
return
if self.app.ui_field_names == "renamed" and "recipient" in body:
body["subject_id"] = body.pop("recipient")
if url.path.endswith("/grant"):
self._dispatch("grant", resource_id=resource_id, **body)
elif url.path.endswith("/revoke"):