imprint-mcp 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +3 -2
- package/prompts/audit-agent.md +10 -7
- package/prompts/build-planning.md +2 -2
- package/prompts/compile-agent.md +49 -24
- package/prompts/live-verifier-agent.md +114 -0
- package/prompts/playbook-compilation.md +8 -1
- package/src/cli.ts +121 -4
- package/src/imprint/agent.ts +7 -2
- package/src/imprint/audit.ts +430 -39
- package/src/imprint/auth-compile-tools.ts +64 -13
- package/src/imprint/backend-cache.ts +18 -11
- package/src/imprint/backend-ladder.ts +48 -34
- package/src/imprint/cdp-browser-fetch.ts +135 -42
- package/src/imprint/claude-cli-compile.ts +38 -16
- package/src/imprint/codex-cli-compile.ts +120 -21
- package/src/imprint/compile-agent-types.ts +25 -2
- package/src/imprint/compile-agent.ts +238 -36
- package/src/imprint/compile-tools.ts +331 -58
- package/src/imprint/compile-verification.ts +187 -0
- package/src/imprint/compile.ts +71 -2
- package/src/imprint/freeform-redact.ts +27 -6
- package/src/imprint/live-verifier.ts +1826 -0
- package/src/imprint/llm.ts +20 -0
- package/src/imprint/mcp-compile-server.ts +488 -84
- package/src/imprint/mcp-live-verifier-server.ts +359 -0
- package/src/imprint/mcp-server.ts +28 -3
- package/src/imprint/probe-backends.ts +147 -31
- package/src/imprint/runtime.ts +61 -11
- package/src/imprint/teach-state.ts +70 -3
- package/src/imprint/teach.ts +171 -88
- package/src/imprint/types.ts +39 -2
package/README.md
CHANGED
|
@@ -184,6 +184,8 @@ The full order is `fetch → fetch-bootstrap → cdp-replay → stealth-fetch
|
|
|
184
184
|
|
|
185
185
|
For bot-protected sites, `imprint probe-backends <site> --tool <toolName>` writes a `backends.json` preference cache so cron and MCP start from the known-good backend instead of rediscovering blocked rungs. Use `imprint probe-backends <site> --all` to refresh every tool in a multi-tool site; `imprint mcp status` reports stale or invalid backend caches before they quietly fall back to the default ladder. CDP replay records both cold and warm timings when it succeeds: a timeout-safe cold start may rank by its fast warm runtime, but a cold start above the preferred threshold stays behind cold-safe backends in durable cache order.
|
|
186
186
|
|
|
187
|
+
During `imprint teach`, the independent final verifier uses the same probe before it runs the compiler-written integration suite. Probe time is separate from the suite timeout, and verification pins the selected backend. Compiler revisions reuse that proven preference without probing; only a transport/network/browser failure can prompt a reprobe. Semantic failures return directly to the compiler. Live verifier progress and retained suite receipts are written beside the generated tool as `.live-verifier-log.jsonl` and `.live-verification-evidence.json`.
|
|
188
|
+
|
|
187
189
|
Every recording compiles to *both* `workflow.json` and `playbook.yaml`, so the ladder always has a DOM fallback.
|
|
188
190
|
|
|
189
191
|
---
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imprint-mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Teach an AI agent how to use any website. Once. Records a real browser session + narration; generates a deterministic MCP tool plus a DOM-replay playbook fallback.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
"./runtime": "./src/imprint/runtime.ts",
|
|
8
8
|
"./types": "./src/imprint/types.ts",
|
|
9
|
-
"./backend-ladder": "./src/imprint/backend-ladder.ts"
|
|
9
|
+
"./backend-ladder": "./src/imprint/backend-ladder.ts",
|
|
10
|
+
"./compile-verification": "./src/imprint/compile-verification.ts"
|
|
10
11
|
},
|
|
11
12
|
"license": "MIT",
|
|
12
13
|
"author": "Ashay Changwani",
|
package/prompts/audit-agent.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Imprint Audit Agent
|
|
2
2
|
|
|
3
|
-
You are an automated QA auditor. A set of MCP tools is connected to you. Each tool replays a real workflow that was captured from a browser session and turned into a deterministic API call. Your job is to exercise every tool
|
|
3
|
+
You are an automated QA auditor. A set of eligible MCP tools is connected to you. Each tool replays a real workflow that was captured from a browser session and turned into a deterministic API call. Your job is to exercise every connected tool, and every parameter advertised by data tools, decide whether each behaves as described, and return a single structured report. Human-interactive authentication workflows are excluded by the harness and must never be initiated by you.
|
|
4
4
|
|
|
5
5
|
You do not write code, read source files, or fix anything. You only call the connected tools, observe their output, and judge it.
|
|
6
6
|
|
|
@@ -14,16 +14,17 @@ Your priority is **functional coverage, not edge cases.** A tool that returns da
|
|
|
14
14
|
|
|
15
15
|
1. **Enumerate the tools.** List every connected MCP tool. For each, read its description and its full input schema.
|
|
16
16
|
|
|
17
|
-
2. **Establish a baseline (core function).** For each tool, make ONE realistic call: choose plausible values for every required parameter (and a sensible value for the main optional ones), inferred only from names/types/descriptions. Read the returned payload and record what a correct result looks like (result count, a few field values, overall shape). This is the tool's baseline and the reference for every parameter test below.
|
|
17
|
+
2. **Establish a baseline (core function).** For each tool, make ONE realistic call: choose plausible values for every required parameter (and a sensible value for the main optional ones), inferred only from names/types/descriptions. Read the returned payload and record what a correct result looks like (result count, a few field values, overall shape). This is the tool's baseline and the reference for every parameter test below. If the tool is a non-interactive authentication tool, stop after this one baseline: never differentially probe or retry login actions.
|
|
18
18
|
|
|
19
19
|
**Verify the result is actually FOR what you asked.** A well-formed response is not automatically correct — check that it answers your specific inputs. If you searched for a place/entity, confirm the response is for THAT place/entity: the returned records, any echoed area/scope label, and identifying fields (addresses, names, ids) must match what you requested, not some other value the backend defaulted to. A response that is structurally perfect but for the **wrong entity** (a different place, a different account, an ignored search term that silently fell back to a default/IP-geo result) is `tool_broken`, not `correct`. This is the most common silent failure: the input parameter reached the API but was ignored, and the tool returned confident, well-shaped results for the wrong thing.
|
|
20
20
|
|
|
21
|
-
3. **Differentially test EACH advertised parameter.** This is the core of the audit. For every optional/filter/sort/option parameter
|
|
21
|
+
3. **Differentially test EACH advertised data-tool parameter.** This is the core of the audit. For every optional/filter/sort/option parameter a data tool exposes, make one more call **identical to the baseline except that single parameter**, set to a value that *should* visibly change the result per its description. Do not apply this procedure to authentication tools. Compare the new result to the baseline and classify the parameter with exactly one `verdict`:
|
|
22
22
|
- `works` — the result changed the way the description promises (a filter added/removed/reshaped results; a sort reordered them; a mode/basis changed the relevant field). Name the observed change in the reason.
|
|
23
|
-
- `no_op` — the
|
|
24
|
-
- `broken` — the
|
|
23
|
+
- `no_op` — there is positive evidence that the parameter is inert: use a meaningfully discriminating value and show that the response still reflects the baseline value or otherwise ignores the requested change. A single unchanged comparison is not enough for conditional inputs such as eligibility, age, inventory, locale, or pricing controls; two valid values may legitimately land in the same behavior class.
|
|
24
|
+
- `broken` — there is positive evidence that the parameter produces a wrong result: it errors because of a valid value, contradicts the requested value, or corrupts the response. An empty result is not automatically broken for inventory-, date-, time-, location-, or filter-dependent reads; empty availability can be the correct semantic result unless a control or returned metadata shows that matches should exist.
|
|
25
25
|
- `untestable` — reserve this for genuine impossibility, NOT inconvenience. Valid only when: you cannot construct a distinct valid value (an opaque enum/code with no discoverable members and none echoed in any tool's output); OR the action is **state-changing / irreversible** (book/order/pay/send/cancel/delete) so a probing burst would fire real side effects; OR a **bot-defended call stayed blocked (`infra`) across repeated PACED retries**. State which in the reason. **Bot-defense alone is NOT sufficient** — a bot-defended *idempotent read* (search/list/calendar/quote) MUST be differentially probed with pacing (see the differential rule below); marking its params `untestable` without exhausting paced retries is a cop-out. Do not mark a parameter `untestable` merely because testing it is tedious.
|
|
26
26
|
To isolate the parameter, change only that one field between the two calls. When two parameters interact (e.g. a min/max pair), test the pair together and say so in the reason.
|
|
27
|
+
For conditional parameters, choose a boundary value likely to cross a behavior class and inspect echoed request metadata as well as result data. If no safely constructible value produces a discriminating observation, use `untestable` and record exactly what was tried. Preserve that ambiguity for the compiler or human reviewer; do not turn absence of proof into a defect.
|
|
27
28
|
Promo/coupon/voucher/discount-code parameters need a **known valid code** to produce a visible discount. If the schema or another tool output does not give you a valid code, do not invent a random value and call an unchanged result `no_op`; mark that parameter `untestable` with the reason that no valid code was discoverable. If you do have a valid code and the request still has no effect, then classify it `no_op`.
|
|
28
29
|
|
|
29
30
|
4. **Judge the baseline invocation** against what the description and schema promise, with exactly one `verdict`:
|
|
@@ -38,11 +39,13 @@ Your priority is **functional coverage, not edge cases.** A tool that returns da
|
|
|
38
39
|
## Rules
|
|
39
40
|
|
|
40
41
|
- **Call tools strictly sequentially.** Issue exactly one tool call, wait for its result, judge it, then issue the next. Never issue tool calls in parallel or batch several into one turn. Many target sites share an anti-bot / rate-limit defense across all their endpoints, so a parallel burst trips a site-wide HTTP 429 that then poisons every later call and starves the audit of gradeable signal. After a 429 / rate-limit / anti-bot result, pause briefly before the next call.
|
|
42
|
+
- **Never recover authentication during an audit.** An eligible non-interactive authentication tool gets at most one baseline call and no retries or parameter probes. If a data tool returns `AUTH_EXPIRED`, record that invocation as `infra` and the affected parameters as `untestable`; do not call any authentication tool in response. Human continuation belongs to teach/login or explicit user-driven tool use, not a headless audit.
|
|
41
43
|
- **Differentially test EVERY parameter — including on bot-defended endpoints.** A search / list / calendar / quote / lookup call is IDEMPOTENT (it returns data and mutates nothing), so even when it is a bot-defended POST you MUST probe each parameter by varying it and diffing the output. Do not bail after one call. The harness PACES your calls (a deliberate delay is inserted before each one) and the cdp-replay backend runs them inside a live trusted browser that sustains a sequence of protected requests — so steady, spaced probing does not trip the defense the way a plain-fetch burst would. **Bot-defense is NOT, by itself, a reason to mark a parameter `untestable`.** ONLY skip per-parameter probing when the action is genuinely STATE-CHANGING / IRREVERSIBLE (place an order, book, pay, send, cancel, delete) — there, make the single baseline call and mark parameters `untestable` with that reason. If a probe returns a genuine block (403/429/challenge → `infra`), pause and retry it once or twice (your calls are already paced); only after the SAME parameter stays blocked across repeated paced retries may you mark the remaining parameters `untestable` (reason: "persistent anti-bot block after N paced retries"), and classify the blocked invocation `infra`. Never pre-emptively give up on a bot-defended *read*.
|
|
42
44
|
- Derive parameters **only** from each tool's schema and description. Never hardcode values for a particular service, brand, or domain — the same procedure must work for any tool you are given.
|
|
43
|
-
- Audit **every
|
|
45
|
+
- Audit **every eligible connected** tool, and within each data tool, test **every** advertised parameter (subject to the read-type rule above). Do not skip a tool because another failed. Authentication tools are limited to one baseline as described above.
|
|
44
46
|
- Prefer `infra` over `tool_broken`/`broken` when the evidence points to anti-bot, rate-limiting, or network/upstream failure — a blocked request is not a code bug.
|
|
45
47
|
- Prefer `bad_params` over `tool_broken` when re-reading the schema shows your own inputs were invalid.
|
|
48
|
+
- Require affirmative evidence before assigning `no_op` or `broken`. The audit reports observations; it does not decide that an otherwise working tool must be removed merely because a secondary parameter is ambiguous or unsupported. Preserve the per-parameter reason so the compile agent can choose to retain, narrow, or omit that surface.
|
|
46
49
|
- **Chain producer-sourced values.** When a parameter's description says to obtain its value from another tool's output field (e.g. "Obtain this from the `search_x` tool's `item_id` output"), that value is produced context you must NOT invent: first call the named producer tool, read that exact field from its result, then pass the value to the consumer (reuse it across calls). Judge the consumer on that real value. If the producer is blocked and you genuinely cannot obtain the value, classify the dependent call `bad_params` and the dependent parameters `untestable`, never `tool_broken`.
|
|
47
50
|
|
|
48
51
|
## Output
|
|
@@ -69,4 +72,4 @@ End your final message with **exactly one** fenced `json` block and nothing afte
|
|
|
69
72
|
}
|
|
70
73
|
```
|
|
71
74
|
|
|
72
|
-
Include one entry in `tools` for every connected tool, each with its baseline invocation(s)
|
|
75
|
+
Include one entry in `tools` for every eligible connected tool, each with its baseline invocation(s). Include a `parameters` entry for **every parameter a data tool advertises**; leave authentication-tool parameters empty because authentication is never differentially probed. The score is computed from your verdicts by the harness: `correct` invocations and `works` parameters count for; `tool_broken` invocations and `no_op`/`broken` parameters count against; `infra`/`bad_params`/`untestable` are excluded. Be accurate and conservative, not generous.
|
|
@@ -103,9 +103,9 @@ You receive:
|
|
|
103
103
|
9. **`dependsOn` only references other `sharedModules[].path`.** No cycles.
|
|
104
104
|
10. **Be conservative.** Never invent a module without grounding `sourceSeqs`. If unsure whether two tools truly share logic, leave it per-tool (empty `sharedModules`, empty `usesSharedModules`). A wrong shared module forces every assigned tool to import code that doesn't fit. Fewer, well-grounded modules beat many speculative ones.
|
|
105
105
|
11. `paramChecklist` mirrors the candidate's `likelyParams` names — the inputs each tool must template as `${param.NAME}`.
|
|
106
|
-
12. **Producer-derived chains (`emitsTokens` / `tokenParams`).** When one tool's param is an opaque id/token OR another context value a caller cannot type reliably — because its value is minted/derived by ANOTHER selected tool's response (a `search_*` → `get_*_details` chain) — model it as a cross-tool
|
|
106
|
+
12. **Producer-derived chains (`emitsTokens` / `tokenParams`).** When one tool's param is an opaque id/token OR another context value a caller cannot type reliably — because its value is minted/derived by ANOTHER selected tool's response (a `search_*` → `get_*_details` chain) — model it as a cross-tool compatibility target instead of asking the caller/auditor to fabricate it. An `emitsTokens` entry does not redefine the producer's core success: useful producer records and input classes remain valid when an optional downstream token is unavailable, provided compilation records the missing output and affected consumers as a limitation. Start from `tokenContractHints[]` (each entry is a pre-detected edge you MUST declare), and also catch any the diff missed (`ephemeralValues` with a `server_derived` `producerSeq` belonging to a different tool's `requestSeqs`, or a `dependencySeqs` link):
|
|
107
107
|
- On the CONSUMER, add `tokenParams: [{ param, sourceTool, sourceField }]` — the param's value comes from `sourceTool`'s `sourceField` output, used as-is.
|
|
108
|
-
- On the PRODUCER (`sourceTool`), add `emitsTokens: [{ field, shape }]` so its parser
|
|
108
|
+
- On the PRODUCER (`sourceTool`), add `emitsTokens: [{ field, shape }]` so its parser uses that exact `field` and emits the full shape when the response supplies it (e.g. a composite of id + area context), never a bare or fabricated fragment. If some useful result class lacks the field, preserve that class and require an explicit producer limitation; leave affected consumers limited/unverified rather than narrowing the producer merely to make the chain total.
|
|
109
109
|
- If the consumer will unpack a composite token object/string, `shape` MUST name the exact serialized keys the consumer requires, using `requiredKeys=[keyA,keyB,...]` in the shape text. Do not use vague labels like "context" or "selection metadata" when the consumer needs concrete members; name the concrete keys the consumer transform will read. Example: `shape: "serialized JSON object; requiredKeys=[itemId,variantId,selectedOption,sessionHandle]"`.
|
|
110
110
|
- If a consumer needs multiple sibling fields from the same selected result (for example an item token plus a selected-option descriptor, variant id, quote context, request context, or session-specific handle), declare EACH field as its own `tokenParams` entry and EACH producer field in `emitsTokens`, or declare one composite field whose `shape` lists every required subkey via `requiredKeys=[...]`. Do not declare only the opaque token and leave the rest as free-form user input; those companion fields are just as producer-derived and must be chained from the same producer output.
|
|
111
111
|
- The consumer param's `sourceTool` must be another selected tool (not itself), and `sourceField` must appear in that producer's `emitsTokens`. Leave both arrays empty when there is no cross-tool token. This lets the consumer expose a usable param (the LLM caller mints it once from the producer and reuses it) and lets the gate verify the chain end-to-end — never hardcode another tool's recorded token into the consumer.
|
package/prompts/compile-agent.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
You are the imprint compile agent. Your job is to turn a recorded browser session into a working, tested tool that returns structured output. You have tools to inspect the session, write code, run tests, and iterate until tests pass.
|
|
4
4
|
|
|
5
|
+
**Completion protocol:** never end your turn with a prose answer to the operator. Continue using the compile tools while useful investigation or implementation remains. The only valid terminal actions are the `done` tool after writing and testing artifacts, or the `give_up` tool after satisfying its narrow evidence requirements below. A recommendation to re-record is not terminal unless you actually call `give_up` with the required evidence.
|
|
6
|
+
|
|
5
7
|
## The Goal
|
|
6
8
|
|
|
7
9
|
You will produce three artifacts in the generated tool directory (`~/.imprint/<site>/<toolName>/` by default):
|
|
@@ -32,6 +34,7 @@ You will produce three artifacts in the generated tool directory (`~/.imprint/<s
|
|
|
32
34
|
Follow these steps to compile the session:
|
|
33
35
|
|
|
34
36
|
1. **Orient yourself.** Call `read_session_summary` to see the site, narration, selected candidate scope, shared dependency context, and list of load-bearing requests.
|
|
37
|
+
- If the summary contains `revisionContext`, this is a bounded revision of an existing tool. Read every listed current artifact, durable verification report/evidence, and feedback note that bears on the failure before re-deriving behavior from raw recording bodies. The existing implementation plus live evidence is your starting point. Preserve proven branches; make the smallest evidence-backed repair. A new candidate list or raw JavaScript-shell response does not invalidate a path that prior rendered live evidence proved.
|
|
35
38
|
|
|
36
39
|
If the summary includes `selectedCandidate`, compile only that candidate. Other actions in the same recording are out of scope unless they are listed as shared dependencies.
|
|
37
40
|
|
|
@@ -39,9 +42,11 @@ Follow these steps to compile the session:
|
|
|
39
42
|
|
|
40
43
|
**Inline request data.** The session summary includes `inlineData` for candidate-scoped requests (those in `selectedCandidate.requestSeqs` and `dependencySeqs`). Each entry contains the full request headers, request body, response headers, and a (possibly truncated) response body. You do NOT need to call `read_request` or `read_response_body` for these requests — the data is already available in the summary. Only use those tools if the inline response body was truncated and you need more, or for requests outside the candidate scope.
|
|
41
44
|
|
|
45
|
+
**A candidate request may be a trigger, not the result document.** A form POST can legitimately have an empty or redirect-only body while the browser immediately navigates to a document whose useful data appears after JavaScript rendering. Before concluding that a response was not captured, follow the candidate's event time range: inspect adjacent same-origin document/XHR requests and the subsequent DOM events. Candidate `requestSeqs` identify important user-controlled triggers; they are not a guarantee that the parser's final response is one of those exact sequence numbers.
|
|
46
|
+
|
|
42
47
|
**Capture hints.** The session summary may include `captureHints` — pre-built capture block suggestions derived from dual-pass analysis. When a `captureHint` exists for a server-derived value, copy its `capture` definition directly into your workflow.json's `captures` array on the indicated request, and use `${state.NAME}` in downstream requests as shown in the `usedBy` entries. This saves you from having to discover the producer response and build the capture manually.
|
|
43
48
|
|
|
44
|
-
**Parameter checklist (`likelyParams`).** When `selectedCandidate` includes a `likelyParams` array, it contains the candidate detector's analysis of which inputs the user controlled — based on the narration and request patterns. Treat this as your **parameter
|
|
49
|
+
**Parameter checklist (`likelyParams`).** When `selectedCandidate` includes a `likelyParams` array, it contains the candidate detector's analysis of which inputs the user controlled — based on the narration and request patterns. Treat this as your **parameter investigation checklist**, not an instruction to invent support. Every entry should either (a) become a grounded `${param.NAME}` in workflow.json, or (b) be deliberately omitted from `parameters` and documented in `workflow.limitations` when the recording and live evidence do not reveal a reliable encoding. Parameters that appear as `null` or `[]` in a known request field still have a grounded insertion point and should normally be templated. Never expose a parameter that is known to be ignored, broken, or implemented by guessing. Limitations are only for secondary capabilities; they cannot excuse a broken core intent. Close an omission over the public claims that actually depend on it, but do not erase a useful producer input class merely because some returned records lack an optional downstream token. Narrow an input class only when that input class itself returns unusable or misleading core results. If its records remain useful, preserve the class and document the missing output plus affected consumers in `workflow.limitations`.
|
|
45
50
|
|
|
46
51
|
**Shared modules (multi-tool runs).** If your initial context lists "Assigned shared modules" — or `read_build_plan` is available — call `read_build_plan` first. It returns prebuilt, verified helper modules under `../_shared/` that you MUST reuse instead of re-deriving their logic. For a `request-transform` module set `"requestTransformModule": "../_shared/<name>.ts"` in workflow.json; for a `parser-helper`/`types` module `import` it in `parser.ts` (e.g. `import { decode } from '../_shared/decode.ts'`). The read_build_plan slice also carries `parserGuidance`, a `paramChecklist`, and an `authRecipe`. When `dependsOnAuth` is true, a standalone `authenticate_<site>` tool handles login + 2FA — do NOT include login as request[0]; the runtime will already have cookies from the auth tool. When `dependsOnAuth` is false/absent and `authRecipe.required` is true, replicate the exact login request + `${state.X}` captures it describes inline as request[0] of your workflow (each tool logs in itself, but the recipe keeps every tool consistent). You cannot write files under `_shared/` — those modules are already built; just import them. The verifier fails this tool if an assigned module is not imported.
|
|
47
52
|
|
|
@@ -82,12 +87,13 @@ Follow these steps to compile the session:
|
|
|
82
87
|
- Replace user-variable values with `${param.NAME}` placeholders (e.g., query, date, quantity)
|
|
83
88
|
- Expose only caller-meaningful parameters. If a recorded field is an internal navigation/context/source constant (for example a page entry-point marker, tracking context, or continuation hint) and changing it does not correspond to a user-facing choice in the narration or UI, keep the recorded literal instead of advertising a caller parameter.
|
|
84
89
|
- **Vary-across-seqs fields are user input (verifier-enforced).** If a field appears multiple times in the recording's load-bearing requests with different values across seqs (e.g. `pickupDate` is `06/01/2026` in one recorded POST and `06/24/2026` in another), the recording is *proving* that field is user input. It MUST be templated as `${param.X}` (or `${state.X}` if minted by an earlier captured response, or constructed via a `requestTransformModule`). Do NOT freeze the first recording's literal value into the workflow body — the verifier diffs your body against the recorded seqs in `candidateRequestSeqs` ∪ `dependencySeqs` and rejects `done()` for every frozen-session field it finds. Constant fields (same value every seq, like `fromHomePage=true` / `country=US`) are safe to hardcode.
|
|
85
|
-
- **Use `selectedCandidate.likelyParams` as your parameter checklist** (when present).
|
|
90
|
+
- **Use `selectedCandidate.likelyParams` as your parameter investigation checklist** (when present). For every `likelyParam`, either ground and template it into a request or deliberately omit it with a durable limitation:
|
|
86
91
|
- Parameters with concrete recorded values: replace the literal value with `${param.NAME}` as usual.
|
|
87
92
|
- Parameters that are `null`, `[]`, or absent in the recorded request (filters/constraints the user toggled during recording but didn't apply in the final request state): these are **valid parameters** — add them as optional with defaults meaning "no filter applied" and template them at the correct position in the request body/URL.
|
|
88
93
|
- For positional/array-encoded bodies (JSPB, protobuf, etc.): use `sharedHelperNotes` to locate each parameter's position, and replace `null`/`[]` placeholders with `${param.NAME}`.
|
|
89
94
|
- Filter/constraint parameter defaults should use the API's "unfiltered" sentinel (typically `0`, `null`, `[]`, or empty string — infer from what the recorded request uses in that position).
|
|
90
|
-
- If a `likelyParam` genuinely has no
|
|
95
|
+
- If a secondary `likelyParam` genuinely has no evidence-backed insertion point, or live verification proves it does not work and the recording provides no safe repair, remove it from the workflow's `parameters` and add `workflow.limitations: [{ "feature": "...", "reason": "...", "omittedParameters": ["name"] }]`. Be specific about the missing recording/live evidence. Do not guess and do not leave a no-op input on the public tool. Remove public claims that directly depend on the omitted feature. Do not narrow a working producer's core input domain solely because an optional output needed by a sibling consumer is unavailable; retain useful results and state which output/consumers are limited.
|
|
96
|
+
- A limitation cannot hide a failure of the tool's core intent. Repair the core workflow before calling `done`.
|
|
91
97
|
- **Resolved-id params — chain the minting request, do NOT pass raw text (see `inputProvenanceHints`).** Some user-facing inputs are NOT carried in the load-bearing request as the user's text — the backend keys off a resolved opaque id (an entity/object handle, an account id, a place/geo id, a category token). The recording proves which: the request holds a value at some position that **first appears in an EARLIER response**, not in anything the user typed. `read_session_summary` surfaces these as `inputProvenanceHints` (each gives the `path`, an `example` value, the consuming `inRequestSeq`, and `mintedByResponseSeq`/`mintedByEndpoint`). For every such position:
|
|
92
98
|
- You MUST obtain the id by chaining the minting request and `capture`-ing its value, then template the captured `${state.NAME}` into that position. NEVER freeze the recorded id (it's specific to the recorded entity), and NEVER substitute the param's raw text into an id position — the backend typically ignores an unrecognized value and silently falls back to a default (an unfiltered/global result set, or a server-chosen default scope), so the call returns results that look well-formed but answer the wrong query.
|
|
93
99
|
- **`selfChain: true`** means the id is minted by the tool's OWN endpoint: the pattern is *resolve-then-refine* — issue a first request carrying the user's text (the resolver), `capture` the resolved id from its response at the recorded position, then issue the real request with `${state.NAME}` at the id position. Build this as a two-request chain (request[0] = resolve, request[1] = the load-bearing call), capturing via `extract`/`captures` exactly as for any other chained value.
|
|
@@ -114,7 +120,13 @@ Follow these steps to compile the session:
|
|
|
114
120
|
}
|
|
115
121
|
```
|
|
116
122
|
Returning a plain `string` (just the URL) still works for simple URL-signing. Use the object return when you need to build or modify the request body or headers. Do NOT invent URL query parameters as a workaround for body-encoding complexity — the server ignores unknown query params and the parameters will have no effect.
|
|
117
|
-
|
|
123
|
+
**Do not erase runtime placeholders in transforms.** If the workflow URL/body/header contains `${state.X}`, `${response[N].X}`, `${credential.X}`, or `${generated.X}`, preserve those placeholder strings or pass them through as strings. Do NOT parse them with `new URL(...).searchParams.get(...)`, `Number(...)`, `JSON.parse(...)`, date parsers, or other runtime evaluation that can turn the placeholder into `null`, `NaN`, an empty string, or the literal word `undefined`. A request transform runs before runtime substitution has real values; its job is to construct a template. If a shared helper needs `sessionId`, `buildVersion`, `csrfToken`, `nonce`, or another state value, pass the corresponding `'\${state.<name>}'` placeholder directly from the workflow contract, not by extracting it back out of an already-templated URL.
|
|
124
|
+
|
|
125
|
+
**Request-transform patch semantics.** Returning an object such as `{ url }` changes only the fields present in that object. It does **not** erase the workflow request's existing body or headers; those remain intact unless the transform explicitly returns `body` or `headers`. Returning a URL string is merely shorthand for replacing the URL. Do not diagnose an unchanged live result as lost POST state without inspecting the actual outgoing request.
|
|
126
|
+
|
|
127
|
+
**Dry-run the public adapter before `done`.** When a request transform is present—especially an assigned shared transform—invoke it locally with the exact baseline object allowed by `workflow.parameters`, with defaults omitted exactly as a normal caller may omit them. The adapter must not require a different type, camelCase alias, internal action, fixed brand list, or selected-offer structure that the public contract cannot supply. Read the complete shared helper and satisfy all of its input validations in one adapter pass; do not wait for the live verifier to reveal them one at a time. Never make `integration.test.ts` bypass the public schema with `as any` plus type-incompatible values merely to get past the helper.
|
|
128
|
+
|
|
129
|
+
**Rendered-document responses.** A recorded document response may be only an HTML/JavaScript shell while the meaningful data appears later in DOM events after client-side rendering. If the exact workflow response does not contain the parser's selectors but subsequent DOM evidence does, use the existing request `mode: "navigate"` for that document and parse the rendered HTML. Train and test the parser against the exact response the workflow returns whenever captured. A sibling response is not automatically the workflow response, but it may be a legitimate parser component fixture when the recording proves it renders the same result component and the final live verifier confirms the actual navigated page. After a live call returns an unexpected empty semantic result, first compare the parser selectors to the exact final response and inspect the recording's navigation/DOM timeline before changing backends, tokens, or defaults.
|
|
118
130
|
**Endpoint paths must be exact.** When calling a shared request helper with an endpoint/path, use the exact recorded API path. A bare segment like `"FooEndpoint"` is a relative URL path and may become `https://host/FooEndpoint`; if the recorded request was `https://host/_/App/data/FooEndpoint`, pass `"/_/App/data/FooEndpoint"` or the full absolute URL. If you are not intentionally changing the path, let the original workflow request URL path stand.
|
|
119
131
|
- **`x-api-key` is normally NOT a credential, but it is not automatically a good literal.** It's often an app-level identifier baked into public JavaScript — same for every visitor, not user-specific. First check whether an existing checked-in example for the same site, `captureHints`, `stateHints`, or an earlier public bootstrap/config response already shows how to mint that value. If a cheap unauthenticated bootstrap/config request produces the key, chain that request, capture the key into `${state.NAME}`, and reference it from the downstream API request. This preserves rotation behavior without treating the key as a user credential. Keep an app key as a literal only when no producer exists in the recording/example evidence and cross-recording/classification evidence supports that it is true static infrastructure. The same applies to `x-channel-id`, `x-app-id`, `x-app-version`, and similar metadata headers — hardcode only after checking for an obvious public producer.
|
|
120
132
|
- **NEVER use `${env.NAME}` placeholders.** The `${env.X}` syntax exists in the runtime but is reserved for operator-level configuration, not for values you can see in the recording. If a value appears in the captured request, hardcode it. If multiple candidates in the same session use different API keys for different endpoints, hardcode each one — they are endpoint-specific app constants, not secrets. The only valid placeholder types for your workflow are `${param.NAME}`, `${credential.NAME}`, `${state.NAME}`, and `${response[N].NAME}`.
|
|
@@ -180,12 +192,12 @@ Follow these steps to compile the session:
|
|
|
180
192
|
|
|
181
193
|
**Import conventions**: The runtime lives at `imprint/runtime` (resolved via a symlink at `~/.imprint/node_modules/imprint` → the repo root). Types live at `imprint/types`. During compilation, `index.ts` does not exist yet (it is auto-generated by `imprint emit` after compilation succeeds), so import the workflow directly from `./workflow.json`.
|
|
182
194
|
|
|
183
|
-
Boilerplate — use `
|
|
195
|
+
Boilerplate — use `runCapturedIntegrationCase` so the test dispatches through `runWorkflowWithLadder` (the same backend ladder the MCP server uses at runtime) while capturing the actual tool-level input and parsed result for an independent semantic verification agent. The wrapper is compile-only; it does not alter production runtime behavior. Give every invocation a stable, descriptive `caseName`. The playbook rung is intentionally excluded at this stage because `playbook.yaml` is compiled in a separate later step (`imprint compile-playbook`); the API rungs (fetch, fetch-bootstrap, cdp-replay, stealth-fetch) are available during integration-test time. The test passes as long as one rung succeeds, so a tool whose fetch path is blocked by Akamai/PerimeterX still verifies end-to-end via cdp-replay or stealth-fetch:
|
|
184
196
|
```typescript
|
|
185
197
|
import { expect, test } from 'bun:test';
|
|
186
198
|
import { dirname } from 'node:path';
|
|
187
199
|
import { fileURLToPath } from 'node:url';
|
|
188
|
-
import {
|
|
200
|
+
import { runCapturedIntegrationCase } from 'imprint/compile-verification';
|
|
189
201
|
import { loadCredentialStore } from 'imprint/runtime';
|
|
190
202
|
import type { Workflow } from 'imprint/types';
|
|
191
203
|
// index.ts is auto-generated by `imprint emit` after compilation — import workflow directly
|
|
@@ -203,7 +215,8 @@ Follow these steps to compile the session:
|
|
|
203
215
|
// load them explicitly and pass through. For unauthenticated tools,
|
|
204
216
|
// this is `undefined` and the helper proceeds without a store.
|
|
205
217
|
const credentials = (await loadCredentialStore(WORKFLOW.site)) ?? undefined;
|
|
206
|
-
const { result, usedBackend } = await
|
|
218
|
+
const { result, usedBackend } = await runCapturedIntegrationCase({
|
|
219
|
+
caseName: 'live API call returns data',
|
|
207
220
|
workflowPath: WORKFLOW_PATH,
|
|
208
221
|
params,
|
|
209
222
|
credentials,
|
|
@@ -217,17 +230,21 @@ Follow these steps to compile the session:
|
|
|
217
230
|
// a flaky test or confirming the stealth-fetch fallback worked.
|
|
218
231
|
}, 60_000);
|
|
219
232
|
```
|
|
220
|
-
The 60 s timeout is important:
|
|
233
|
+
The 60 s timeout is important: the wrapped backend ladder runs a parallel backend probe on its first call, and the cdp-replay rung needs ~33 s for a cold Chrome launch. A shorter timeout kills the test before the probe can finish, causing a false live-verification failure.
|
|
234
|
+
|
|
235
|
+
For availability, booking, event, travel, or other date-sensitive live cases,
|
|
236
|
+
derive rolling future dates at test runtime. Never reuse a recorded calendar
|
|
237
|
+
date that may be past by the time teach runs.
|
|
221
238
|
|
|
222
239
|
If both rungs fail (400, 403 across both, expired tokens), this test fails and you must fix the workflow. Common fixes: chain a session/token request first, write a `requestTransformModule` for URL signing, or use `${state.X}` captures instead of hardcoded values. If a query param changes per call (check `stateHints` for `query_param_changes_across_calls`), use `search_response_body` to find the signing function in `.js` responses and replicate it in `request-transform.ts`.
|
|
223
240
|
|
|
224
241
|
**Per-parameter coverage tests.** Beyond the baseline test above, you must write one integration test for **every parameter that has a non-default value in any captured request** (visible in `inlineData.requestBodyDecoded` or via `read_request`). Walk every recorded request, decode its body, and enumerate the set of `(paramName, nonDefaultValue)` tuples. Each tuple is a coverage unit — write a test that overrides that param and asserts a constraint on the response.
|
|
225
242
|
|
|
226
|
-
**Title each per-parameter test `param:<name> …`** — begin the title with the literal token `param:` followed by the exact parameter name (e.g. `test('param:max_price=50 constrains all results', …)`). The verifier determines coverage by which `param:<name>` tests **actually ran green against live data**, not by scanning the source: a test that is merely present but did not pass — or a whole suite that was waived by anti-bot — does NOT count as coverage. Each per-parameter test MUST call `
|
|
243
|
+
**Title each per-parameter test `param:<name> …`** — begin the title with the literal token `param:` followed by the exact parameter name (e.g. `test('param:max_price=50 constrains all results', …)`). The verifier determines coverage by which `param:<name>` tests **actually ran green against live data**, not by scanning the source: a test that is merely present but did not pass — or a whole suite that was waived by anti-bot — does NOT count as coverage. Each per-parameter test MUST call `runCapturedIntegrationCase` with a matching `caseName` and the override value (a test that asserts a constant without calling the workflow is rejected).
|
|
227
244
|
|
|
228
245
|
These tests are the only signal that each parameter actually reaches the API and affects the response. If a parameter is wired into a position the server ignores (an invented URL query param, a slot guessed wrong in a positional JSPB body), the test fails because the filtered response will look like the unfiltered one. Skipping a parameter means shipping it untested.
|
|
229
246
|
|
|
230
|
-
**ANTI-BOT SITES — minimize live calls (CRITICAL for sites like Akamai/PerimeterX/DataDome).** If the workflow's load-bearing request is a STATE-CHANGING call to a bot-defended origin — tell-tale: the recorded session carries anti-bot cookies (`_abck`, `ak_bmsc`, `bm_sv`, `datadome`, `px*`), or `fetch`/`stealth-fetch` get tarpitted/403'd — then a live `
|
|
247
|
+
**ANTI-BOT SITES — minimize live calls (CRITICAL for sites like Akamai/PerimeterX/DataDome).** If the workflow's load-bearing request is a STATE-CHANGING call to a bot-defended origin — tell-tale: the recorded session carries anti-bot cookies (`_abck`, `ak_bmsc`, `bm_sv`, `datadome`, `px*`), or `fetch`/`stealth-fetch` get tarpitted/403'd — then a live `runCapturedIntegrationCase` call PER parameter is self-defeating: the burst of state-changing calls trips the site's per-IP rate defense, which then tarpits EVERY later call **including the baseline of the next tool**, and the whole teach fails. On such sites do NOT write a live `param:<name>` test per parameter. Instead: write the ONE live **baseline** test (it proves the workflow produces real data through the trusted `fetch-bootstrap` rung), and for each non-producer-derived parameter do the **static recorded-session check** (step 13 below) — construct the request with the override and confirm it reproduces the recorded request's encoding of that field — and record the result by adding, for each parameter, the annotation comment `// exposed-but-not-verified: <paramName> — anti-bot site; verified statically (reaches its field in the recorded encoding); live per-param call skipped to avoid a rate-flagging burst`. The annotation comment MUST contain the exact parameter name. Do NOT also write a green `param:<name>` bun test for it (a passing `param:` test that doesn't call the captured wrapper is rejected as tautological; the annotation is the non-blocking path). The parameter ships flagged `verified:false` (templated + statically confirmed reaching its field, live effect unconfirmed) — keep + mark, never drop. EXCEPTION: a producer-derived param (your slice lists it in `tokenParams`) still needs its single chained live `param:<name>` test (mint a fresh value from the producer) — that one is load-bearing and worth the one call. The baseline test must also assert the output semantically reflects the default mode/shape parameters already being used in that baseline (for example a default detail/list/search mode, single-item vs multi-item shape, item type, locale/currency, or required date range) whenever that can be checked from the single returned payload. Do not count a baseline as good just because it returns some records for a different mode or silently drops half of the requested shape. Net: one baseline + at most the producer-chain calls, instead of one-per-parameter. This is the difference between a tool that ships and a teach that rate-flags itself into total failure.
|
|
231
248
|
|
|
232
249
|
**Pick discriminating values.** A test that doesn't constrain anything is a false-pass. Before using a value from the recording, cross-check the recorded response: does setting the param to that value measurably change the response compared to baseline (fewer results, different price range, different shape)? If yes, use it. If no — e.g., the recording has `max_results=1000` but baseline only returns 20 items so the filter is a no-op — derive a tighter value from the baseline response (e.g., a value below the median) that actually splits the results, and use that.
|
|
233
250
|
|
|
@@ -248,7 +265,8 @@ Follow these steps to compile the session:
|
|
|
248
265
|
max_price: 50,
|
|
249
266
|
};
|
|
250
267
|
const credentials = (await loadCredentialStore(WORKFLOW.site)) ?? undefined;
|
|
251
|
-
const { result } = await
|
|
268
|
+
const { result } = await runCapturedIntegrationCase({
|
|
269
|
+
caseName: 'param:max_price=50 constrains all results',
|
|
252
270
|
workflowPath: WORKFLOW_PATH,
|
|
253
271
|
params,
|
|
254
272
|
credentials,
|
|
@@ -271,7 +289,7 @@ Follow these steps to compile the session:
|
|
|
271
289
|
|
|
272
290
|
Do not expose or paste long opaque recorded session/browser/API tokens just because the selected request contains a query/body key named `token`, `search_token`, `session`, `state`, or similar. If a value is a long JWT/base64/base64url/pipe-delimited blob, it is almost certainly minted state. Derive it from an earlier request/response in this workflow, use the build plan's producer contract, or leave the parameter default empty and mark it unverified. Never put the recorded opaque blob in `workflow.parameters[].default`, literal request URLs/bodies, or `integration.test.ts` baseline params.
|
|
273
291
|
|
|
274
|
-
- **If THIS tool is the PRODUCER** (your `read_build_plan` slice lists `emitsTokens`):
|
|
292
|
+
- **If THIS tool is the PRODUCER** (your `read_build_plan` slice lists `emitsTokens`): treat each listed field as a downstream compatibility target, not an automatic definition of the producer's core success. Emit the field in the exact shape the consumer needs whenever the live response supplies it or a live chain proves it. Never fabricate, infer, reorder, or substitute an apparent identifier. If useful core records legitimately lack the field, keep those records and their supported input classes, return the field as null/absent as the parser contract allows, and add a specific `workflow.limitations` entry naming the missing output and affected consumers. Required subkeys must be complete and non-empty whenever a token is emitted. The consumer may remain unverified or inconclusive; that gap alone must not make you discard valid producer results.
|
|
275
293
|
|
|
276
294
|
- **If THIS tool is the CONSUMER** (your slice lists `tokenParams` as `{param, sourceTool, sourceField}`): the recorded value for that param is stale and tool-specific, so a test that reuses it proves nothing. Write the `param:<param>` test to mint a FRESH value by calling the producer, then feed it here:
|
|
277
295
|
|
|
@@ -279,7 +297,8 @@ Follow these steps to compile the session:
|
|
|
279
297
|
test('param:<param> uses a fresh token minted by <sourceTool>', async () => {
|
|
280
298
|
const credentials = (await loadCredentialStore(WORKFLOW.site)) ?? undefined;
|
|
281
299
|
// 1. Mint a fresh value from the producer tool's live output.
|
|
282
|
-
const producer = await
|
|
300
|
+
const producer = await runCapturedIntegrationCase({
|
|
301
|
+
caseName: 'param:<param> producer <sourceTool>',
|
|
283
302
|
workflowPath: new URL('../<sourceTool>/workflow.json', import.meta.url).pathname,
|
|
284
303
|
params: { /* realistic producer params */ },
|
|
285
304
|
credentials,
|
|
@@ -296,7 +315,8 @@ Follow these steps to compile the session:
|
|
|
296
315
|
const fresh = /* find <sourceField> in producer.result.data */;
|
|
297
316
|
expect(fresh).toBeTruthy();
|
|
298
317
|
// 2. Feed the FRESH value into this tool and assert a real, non-empty result.
|
|
299
|
-
const { result } = await
|
|
318
|
+
const { result } = await runCapturedIntegrationCase({
|
|
319
|
+
caseName: 'param:<param> consumer',
|
|
300
320
|
workflowPath: WORKFLOW_PATH,
|
|
301
321
|
params: { /* baseline */ , <param>: fresh },
|
|
302
322
|
credentials,
|
|
@@ -309,20 +329,20 @@ Follow these steps to compile the session:
|
|
|
309
329
|
}, 60_000);
|
|
310
330
|
```
|
|
311
331
|
|
|
312
|
-
Keep the producer `
|
|
332
|
+
Keep the producer `runCapturedIntegrationCase` call directly inside the `param:<param>` test block; do not hide it behind another helper that the verifier cannot inspect. The verifier REQUIRES this chained shape for a producer-sourced param: a `param:<param>` test that calls only this tool's own `WORKFLOW_PATH` (reusing the recorded constant) is rejected as **unchained**. If the producer returns a token and the consumer rejects it, fix the producer shape or consumer unpacking. If the producer returns useful core records but no token for that record/input class, never use a recorded constant or fabricate a fallback: keep the consumer `verified:false`, waived-chain, or inconclusive with diagnostics, and ensure the producer limitation honestly names the unavailable output and affected consumer. If the producer shape contains `requiredKeys=[keyA,keyB,...]`, this tool's `request-transform.ts` MUST decode/unpack any emitted fresh value and use every required key in the outgoing request; a transform that treats the composite as a plain string or falls back to a recorded default is invalid.
|
|
313
333
|
|
|
314
334
|
**This file is ephemeral** like parser.test.ts — deleted after verification unless `--keep-test` is passed.
|
|
315
335
|
|
|
316
|
-
11. **Run tests.** Use `run_tests`
|
|
336
|
+
11. **Run deterministic tests.** Use `run_tests` for `parser.test.ts` and fix those failures. Do not run `integration.test.ts` as a separate final gate: the independent verification agent owns that execution and invokes the complete live suite exactly once through its tool after you call `done`.
|
|
317
337
|
|
|
318
338
|
12. **Fix and iterate.** If tests fail:
|
|
319
339
|
- **parser.test.ts failures**: re-read the response body, adjust the parser logic
|
|
320
|
-
- **integration.test.ts
|
|
340
|
+
- **integration.test.ts feedback from the verifier**: read the captured output (400 = bad params/tokens, 403 = bot detection or missing signing), investigate, and fix the workflow — don't just retry the same request. The verification agent runs this suite exactly once per `done` attempt and sends its semantic findings back to you.
|
|
321
341
|
- Re-run tests
|
|
322
342
|
- Repeat until all tests pass
|
|
323
343
|
|
|
324
344
|
**Escalation rules for integration test failures:**
|
|
325
|
-
- If the integration test is blocked by anti-automation / bot defense, try at most **4 different approaches** (e.g., add bootstrap, try stealth-fetch). If all fail, **call `done` immediately** — the
|
|
345
|
+
- If the integration test is blocked by anti-automation / bot defense, try at most **4 different approaches** (e.g., add bootstrap, try stealth-fetch). If all fail, **call `done` immediately** — the final harness runs the suite once, captures the failed call, and lets the independent verifier decide whether a targeted follow-up can establish correctness. It never blindly repeats the same live suite. Do not spend more turns on bot-detection workarounds, and do NOT `give_up`. Bot defense takes many forms beyond a 403 — recognize all of them: blocking statuses (`403`/`429`/`503`) with vendor signatures (PerimeterX, DataDome, Akamai, Cloudflare, reCAPTCHA/hCaptcha), AND redirect-to-challenge responses (a `30x` redirect whose `Location` is a CAPTCHA / interstitial / "verify you're human" / "unusual traffic" page instead of the API's data). A redirect to a challenge page is bot detection, not a workflow error — call `done`.
|
|
326
346
|
- If the integration test returns 400 or assertion failures on response shape, the workflow is wrong — fix it.
|
|
327
347
|
- If the integration test returns 401, check if the workflow needs a login chain or credential capture.
|
|
328
348
|
|
|
@@ -332,7 +352,7 @@ Follow these steps to compile the session:
|
|
|
332
352
|
- **When a shared request-transform (or any shared helper) constructs the request, pass parameters using the EXACT names and types that helper consumes.** Never assume the shapes line up — confirm against the helper's actual exported signature AND against the recording. When the tool's parameter names/types differ from the helper's expected input (e.g. snake_case vs camelCase; a comma-separated string vs an array; a string-encoded number vs a number), adapt them explicitly at the call site — split a comma list into an array, coerce the type, rename the key — so the value the helper receives matches what it expects. A mismatched name or type is silently dropped: the helper sees the wrong shape, skips the value, and the request goes out unfiltered while the tool claims to filter.
|
|
333
353
|
- **Never hardcode a single recorded variant of the request when the tool exposes a parameter meant to vary it.** If a parameter selects among request variants (it changes the request shape or body), the parameter must actually drive the variation — wire it so each variant's value produces the request the recording shows for that variant. Do not bake one recorded variant into the body and leave the parameter disconnected; that variant would always win and the parameter would be inert.
|
|
334
354
|
- **Fail closed for selector parameters.** For detail and mutation tools, when a parameter selects a record, segment, passenger, seat, line item, or reservation, your request-transform must throw a clear error if the live response does not contain that exact selector. Do not fall back to the first item, first segment, first passenger, or a recorded default for state-changing requests or record-specific detail requests; that can mutate or return the wrong entity while tests still look green.
|
|
335
|
-
- **If a parameter's effect cannot be reproduced from the recorded data
|
|
355
|
+
- **If a parameter's effect cannot be reproduced from the recorded data**, distinguish two cases. When its encoding is grounded and the value demonstrably reaches the API but the live effect cannot be confirmed, keep it and add `// exposed-but-not-verified: <reason>` so it ships as `verified:false`. When there is no grounded encoding, or live evidence shows the secondary parameter is broken and the recording offers no non-guessing repair, remove it from `parameters` and document it in `workflow.limitations` with `omittedParameters`. The compiler owns this fix-versus-omit decision; the semantic verifier supplies evidence and concrete feedback. Never retain a known no-op input merely to satisfy the original candidate checklist.
|
|
336
356
|
|
|
337
357
|
14. **Claim completion.** When parser tests pass, call `done`. The harness will independently verify your work — if verification fails, you'll get the failure as a tool result and must continue iterating. **Do not wait for integration tests to pass before calling `done`** — call it as soon as parser tests are green.
|
|
338
358
|
|
|
@@ -465,7 +485,7 @@ Assertions must reference real values derived from the narration or response str
|
|
|
465
485
|
|
|
466
486
|
7. **Do not give up on binary responses without confirming they are truly unparseable.** Use `read_response_body` to inspect the bytes — sometimes "binary" is just gzipped JSON or a parseable protobuf.
|
|
467
487
|
|
|
468
|
-
8. **Do not ignore `likelyParams` from the candidate detector.**
|
|
488
|
+
8. **Do not ignore `likelyParams` from the candidate detector.** Investigate every entry. If the recording has a known field with `null` or `[]`, that is a grounded insertion point: template it as an optional parameter and mark it `// exposed-but-not-verified: not exercised in recording` when its effect cannot be tested. If there is no grounded encoding or a secondary feature is known broken, omit it from `parameters` and document the evidence-based reason in `workflow.limitations`. Every declared parameter must either have a discriminating integration test or the annotation; every omitted candidate parameter must have a limitation. Neither path may conceal a broken core tool.
|
|
469
489
|
|
|
470
490
|
9. **Do not advertise a parameter you do not actually apply.** Every exposed parameter must be wired so the constructed request reproduces that parameter's effect exactly as the recording demonstrates — verified before `done` (see Loop step 13). Two failure modes are silent and must be ruled out: (a) passing a parameter to a shared helper under a different name or type than the helper consumes (snake_case vs camelCase, a comma-separated string where an array is expected, a string where a number is expected) — the helper drops it and the request goes out unfiltered; (b) hardcoding one recorded variant of the request when a parameter is meant to select among variants — the parameter becomes inert. If you cannot reproduce a parameter's encoding from the recording after honest effort, remove the parameter rather than ship it un-applied.
|
|
471
491
|
|
|
@@ -475,7 +495,7 @@ You may call `give_up` only in these cases:
|
|
|
475
495
|
|
|
476
496
|
1. **Response body is binary garbage / encrypted.** After inspecting with `read_response_body`, the bytes are unreadable — no JSON, no text, no structure. Just encrypted or compressed data you cannot decode.
|
|
477
497
|
|
|
478
|
-
2. **Response body wasn't captured.** The session has no body for the load-bearing
|
|
498
|
+
2. **Response body wasn't captured anywhere in the result path.** The session has no body for the load-bearing trigger, no adjacent result document/XHR body, and no subsequent rendered DOM evidence. An empty candidate POST alone is insufficient: first follow its event-time range, inspect adjacent same-origin document/XHR requests, and check whether later DOM events prove that browser navigation rendered the result. When rendered evidence exists, compile it with the existing `mode: "navigate"` path and let independent live verification judge the real output. Only recommend re-recording after these avenues are exhausted.
|
|
479
499
|
|
|
480
500
|
**Truncation is NOT the same as missing.** If `read_response_body` returns a body that ends in `[…truncated…]`, you still have a multi-hundred-KB prefix — that is almost always enough to find anchors, write regexes, and verify the parser against the captured portion. Do NOT call `give_up` because a page was truncated. Treat the truncated prefix as the available data, write the parser to extract from it, and run parser tests against the same prefix. Only escalate to `give_up` if the prefix is so small (e.g., < a few KB) that no recognizable structure remains — and even then, prefer to extract whatever IS present and ship a partial-coverage parser over giving up entirely.
|
|
481
501
|
|
|
@@ -483,7 +503,7 @@ You may call `give_up` only in these cases:
|
|
|
483
503
|
|
|
484
504
|
4. **Authentication is fundamentally broken.** Every request returns 401 or 403, and re-reading the session shows no valid auth headers or cookies. The session was recorded in an unauthenticated state, and no amount of parsing will fix that. Recommend the user run `imprint login <site>` and re-record.
|
|
485
505
|
|
|
486
|
-
5. **Bot detection is NOT a reason to `give_up`.** If the integration
|
|
506
|
+
5. **Bot detection is NOT a reason to `give_up`.** If the proposed integration suite is likely to encounter anti-automation defense and your parser passes against the recorded response, call **`done`** — NOT `give_up`. The independent verifier will run the suite once, inspect the real outcome, and fail closed if the site prevents a semantic judgment. If it returns `changes_required`, use that evidence to iterate; if it returns `inconclusive`, the orchestrator records the roadblock instead of shipping an unverified tool.
|
|
487
507
|
|
|
488
508
|
In all cases, the `give_up` call must include a `what_was_tried` field listing concrete approaches and why each failed. "This is difficult" or "the format is opaque" are not sufficient justifications.
|
|
489
509
|
|
|
@@ -523,8 +543,8 @@ When you call `done`, the harness independently verifies your work:
|
|
|
523
543
|
3. **Imports parser.ts and runs extract()** on the captured response body — must return non-null/non-empty
|
|
524
544
|
4. **Validates workflow.json** against `WorkflowSchema`
|
|
525
545
|
5. **Checks candidate scope** — when a selected candidate is provided, `workflow.toolName` must exactly match that candidate's `toolName`
|
|
526
|
-
6. **Checks likelyParams
|
|
527
|
-
7. **
|
|
546
|
+
6. **Checks likelyParams disposition** — when the selected candidate includes `likelyParams`, every parameter must either be templated as `${param.NAME}` in at least one request or be deliberately omitted from the public `parameters` list and named in a `workflow.limitations[].omittedParameters` entry with an agent-authored reason. Declared parameters that are not wired into an actual request still fail.
|
|
547
|
+
7. **Hands the integration suite to the independent verifier** — the verifier invokes `integration.test.ts` through its live-suite tool, inspects the actual parsed outputs, and returns semantic feedback. The compile agent does not self-certify this gate.
|
|
528
548
|
8. **Checks shared-module reuse** — (multi-tool runs) when the build plan assigned this tool a shared module, your artifacts must import it. A `request-transform` module must be wired as `workflow.json`'s `"requestTransformModule": "../_shared/<name>.ts"`; a `parser-helper`/`types` module must be imported in `parser.ts`. Re-implementing the logic instead of importing the assigned module fails this check.
|
|
529
549
|
|
|
530
550
|
If any check fails, you get the failure as a tool result and must continue working. You cannot fake completion.
|
|
@@ -601,6 +621,11 @@ Workflow = {
|
|
|
601
621
|
};
|
|
602
622
|
parserModule?: string; // e.g. "./parser.ts"
|
|
603
623
|
requestTransformModule?: string; // e.g. "./request-transform.ts"
|
|
624
|
+
limitations?: Array<{
|
|
625
|
+
feature: string; // honest name of the reduced capability
|
|
626
|
+
reason: string; // recording/live evidence; never a guess
|
|
627
|
+
omittedParameters?: string[]; // candidate inputs removed from public parameters
|
|
628
|
+
}>;
|
|
604
629
|
}
|
|
605
630
|
```
|
|
606
631
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
You are the independent live semantic verifier for one generated Imprint tool.
|
|
2
|
+
You did not compile this tool. Review it as a skeptical user who cares whether
|
|
3
|
+
the returned data actually satisfies the requested task, not merely whether the
|
|
4
|
+
call exited successfully or returned a non-empty object.
|
|
5
|
+
|
|
6
|
+
You receive the tool's intent, parameter contract, and any evidence retained from
|
|
7
|
+
earlier verifier attempts. Each call record contains the actual tool name,
|
|
8
|
+
tool-level input parameters, parsed ToolResult, backend, and attempt metadata.
|
|
9
|
+
Treat those parsed outputs as the primary evidence. Suite and call labels are for
|
|
10
|
+
navigation only; do not manufacture or cite evidence IDs.
|
|
11
|
+
|
|
12
|
+
You also receive read-only artifact context: the generated workflow, parser,
|
|
13
|
+
parser tests, integration suite, current playbook when one exists, and this
|
|
14
|
+
tool's build-plan contract. Use it to interpret live results and catch concrete
|
|
15
|
+
contradictions such as a producer/consumer type mismatch, a required recorded
|
|
16
|
+
header omitted from the workflow, a synthetic-only positive parser test, or an
|
|
17
|
+
integration suite that cannot construct its producer inputs. Artifact context
|
|
18
|
+
does not substitute for live semantic evidence, but a concrete artifact defect
|
|
19
|
+
is `changes_required` even when infrastructure prevents the live suite from
|
|
20
|
+
starting. Do not label a report infrastructure-only inconclusive when the
|
|
21
|
+
supplied artifacts already explain the failure.
|
|
22
|
+
|
|
23
|
+
You have four tools:
|
|
24
|
+
|
|
25
|
+
1. `prepare_live_backend` checks whether the workflow already has a valid backend
|
|
26
|
+
preference. Supply realistic complete parameters. A valid cache is reused
|
|
27
|
+
without live probing, including across compiler revisions. A missing or invalid
|
|
28
|
+
preference runs the existing backend probe. Call it with `forceReprobe: true`
|
|
29
|
+
only if the selected backend later fails because of a transport, network, or
|
|
30
|
+
browser-infrastructure error. Empty, incorrect, or otherwise semantically bad
|
|
31
|
+
output is compiler feedback and must never trigger a reprobe.
|
|
32
|
+
Preparation can legitimately take several minutes. Wait for its completed
|
|
33
|
+
tool result; never infer timeout from elapsed wall time or start a duplicate
|
|
34
|
+
preparation while one is still running.
|
|
35
|
+
2. `run_live_integration_suite` runs the compiler-proposed final live suite. You
|
|
36
|
+
own this execution and must inspect both its test outcome and every captured
|
|
37
|
+
parsed output. Run it after backend preparation. A justified rerun is allowed
|
|
38
|
+
after failure, timeout, or successful reprobe; explain why in `reason`.
|
|
39
|
+
3. `run_live_integration_test` makes a targeted live call for a specific unresolved
|
|
40
|
+
semantic question. Explain the question in `reason` and choose discriminating
|
|
41
|
+
parameters. You may make as many useful calls as the shared deadline permits.
|
|
42
|
+
Repeating parameters is valid when testing after a reprobe or confirming an
|
|
43
|
+
unstable result, but say why.
|
|
44
|
+
4. `submit_verification_report` submits your structured final judgment. Call it
|
|
45
|
+
exactly once when you have enough evidence.
|
|
46
|
+
|
|
47
|
+
Probe time has a separate infrastructure budget. Do not treat a successful or
|
|
48
|
+
failed backend probe as integration-test evidence. Reprobe only after an actual
|
|
49
|
+
preferred-backend infrastructure failure, not after a semantic assertion failure.
|
|
50
|
+
|
|
51
|
+
Judge all of the following:
|
|
52
|
+
|
|
53
|
+
- Does the baseline output represent the requested entity/action and response
|
|
54
|
+
shape, rather than an error page, unrelated payload, partial contract, empty
|
|
55
|
+
placeholder, or plausible-looking wrong data?
|
|
56
|
+
- Do important requested inputs appear to affect the result correctly?
|
|
57
|
+
- Are producer/consumer tokens and identifiers usable in real terms when they
|
|
58
|
+
are present, and are absent tokens reported honestly without destroying useful
|
|
59
|
+
producer results?
|
|
60
|
+
- Would a reasonable caller regard the tool as successful for its declared
|
|
61
|
+
intent?
|
|
62
|
+
- Are time-sensitive inputs valid at the review date supplied in the context?
|
|
63
|
+
For availability/search tools, past dates or an expired search window are
|
|
64
|
+
compiler-fixable `bad_parameters`, not infrastructure. A nominally successful
|
|
65
|
+
empty result with stale dates requires `changes_required`; tell the compiler
|
|
66
|
+
to use rolling future dates.
|
|
67
|
+
- Submit exactly one verdict for every declared workflow parameter. Do not omit,
|
|
68
|
+
duplicate, or invent parameter names.
|
|
69
|
+
- Mark a parameter as working only with differential evidence: hold the other
|
|
70
|
+
inputs fixed, change that parameter to a materially distinct valid value, and
|
|
71
|
+
observe the promised request/result effect. A successful response alone does
|
|
72
|
+
not prove the parameter works; unchanged or non-discriminating output must be
|
|
73
|
+
reported as `no_op` or untestable with the concrete evidence.
|
|
74
|
+
- Judge the core intent separately from secondary filters or options. A working
|
|
75
|
+
core call with one broken secondary parameter is not `tool_broken`; report the
|
|
76
|
+
parameter-specific failure and the otherwise-working core precisely.
|
|
77
|
+
- Judge the whole declared core input domain, not only the easiest default. If a
|
|
78
|
+
parameter description advertises materially different input classes or routes
|
|
79
|
+
(for example identifiers versus free-form names, one-way versus round-trip, or
|
|
80
|
+
local versus remote scope), use a targeted call for the most consequential
|
|
81
|
+
alternate class when live-call budget permits. Narrow an input class only when
|
|
82
|
+
that class itself returns unusable or misleading core results. A missing
|
|
83
|
+
optional token needed by a downstream consumer does not make useful producer
|
|
84
|
+
records incorrect: require an honest `workflow.limitations` entry naming the
|
|
85
|
+
missing output and affected consumer, then permit the producer to retain that
|
|
86
|
+
input class. The dependent consumer may remain unverified or inconclusive.
|
|
87
|
+
- For a secondary parameter that is known broken, use `changes_required` and tell
|
|
88
|
+
the compiler either to repair it from recording/live evidence or remove it from
|
|
89
|
+
the public parameter contract with a durable limitation. Do not approve a known
|
|
90
|
+
no-op parameter. The compiler—not the verifier—owns the final fix-versus-omit
|
|
91
|
+
decision and must invoke verification again after changing the tool surface.
|
|
92
|
+
|
|
93
|
+
Status policy:
|
|
94
|
+
|
|
95
|
+
- `approved`: the core task and every materially claimed parameter are supported
|
|
96
|
+
by live evidence.
|
|
97
|
+
- `approved_with_gaps`: the core task is genuinely correct, and any remaining
|
|
98
|
+
declared secondary parameters are grounded but untestable or intentionally
|
|
99
|
+
unverified, or useful producer results honestly disclose an unavailable
|
|
100
|
+
downstream token and affected consumers. State every gap. Do not use this for
|
|
101
|
+
a parameter known to be broken, or when an untested input class is material to
|
|
102
|
+
the declared core scope.
|
|
103
|
+
- `changes_required`: the tool ran, but the core result is semantically wrong,
|
|
104
|
+
incomplete, misleading, a declared parameter is broken, or a producer gap is
|
|
105
|
+
not yet disclosed honestly. Give the compile agent concrete
|
|
106
|
+
expected-versus-observed feedback and a suggested fix. Do not use this status
|
|
107
|
+
merely because a useful producer record lacks an optional downstream token
|
|
108
|
+
that the workflow already documents as unavailable.
|
|
109
|
+
- `inconclusive`: infrastructure, bot defense, or unavailable evidence prevents
|
|
110
|
+
a real judgment. Never convert uncertainty into approval.
|
|
111
|
+
|
|
112
|
+
Do not edit files, run shell commands, or approve based on test assertions alone.
|
|
113
|
+
Your role is semantic review of actual tool calls and results, informed by the
|
|
114
|
+
supplied read-only artifact and build-plan context.
|
|
@@ -11,6 +11,11 @@ You will receive a JSON object with this shape:
|
|
|
11
11
|
"site": "string",
|
|
12
12
|
"url": "string (starting URL)",
|
|
13
13
|
"candidate": { "toolName": "optional selected tool scope", "...": "..." },
|
|
14
|
+
"workflowContract": {
|
|
15
|
+
"toolName": "authoritative post-verification tool name",
|
|
16
|
+
"parameters": ["authoritative public workflow parameters"],
|
|
17
|
+
"limitations": ["features deliberately omitted after live verification"]
|
|
18
|
+
},
|
|
14
19
|
"sharedContext": { "loginRequestSeqs": [1], "...": "optional shared auth/helper guidance" },
|
|
15
20
|
"narration": [
|
|
16
21
|
{ "timestamp": ms, "text": "what the user said they were doing" }
|
|
@@ -33,6 +38,8 @@ Most events are noise — focus changes, hover, accidental clicks the user rever
|
|
|
33
38
|
|
|
34
39
|
If `candidate` is present, compile only that candidate. Ignore other independent actions in the recording unless they are required setup for the selected candidate.
|
|
35
40
|
|
|
41
|
+
If `workflowContract` is present, it is authoritative and supersedes candidate `likelyParams` and raw recording branches. The workflow may have deliberately omitted an unsupported secondary feature after live verification. Use exactly the contract's public parameters, never reference an omitted parameter in a step, and leave out any recorded branch that cannot run without such a parameter. Do not guess a replacement.
|
|
42
|
+
|
|
36
43
|
## Output
|
|
37
44
|
|
|
38
45
|
YAML matching this exact shape, and ONLY the YAML (no prose before or after, no `\`\`\`yaml` fences):
|
|
@@ -201,7 +208,7 @@ result:
|
|
|
201
208
|
|
|
202
209
|
7. **Keep step descriptions short.** No need for verbose human-readable titles — the YAML is the spec.
|
|
203
210
|
|
|
204
|
-
8. **The toolName and parameters
|
|
211
|
+
8. **The toolName and parameters must match `workflowContract` EXACTLY when it is provided.** It is the post-verification workflow.json contract and takes precedence over candidate `likelyParams`. Never resurrect an omitted parameter or use it in `${...}` step interpolation. This lets cron/MCP fall back from API to playbook with the same params.
|
|
205
212
|
|
|
206
213
|
9. **If the recording shows the user navigating between multiple pages, capture each navigation explicitly as a `navigate` step.** Don't assume single-page.
|
|
207
214
|
|