Repository navigation
🌐 Fetch retained HTTP observations from Markdown #456
Description
Story
As an Executable.md author, I want to perform an authorized HTTP read with <Fetch>, so a document or workflow can obtain external information and present bounded results to a network-denied Agent without giving the Agent network access or credentials.
Motivation
Executable.md already has contextual API.Fetch and exports its fetch operation for eval code, but there is no registered Markdown component. Authored workflows therefore have to drop into JavaScript for an ordinary HTTP read, and constrained Agent-generated XMD has no declarative network operation it can request after #369 admits the pinned component identity and bounded request.
The adversarial implementation target in PR #181 needs current pull-request reviews, comments, and checks. The Agent itself remains network-denied under #302; XMD performs the authorized read and includes the retained response in the Agent's prompt.
This is the general read boundary. It does not add a Git-host-specific read merely because the first consumer uses GitHub HTTP APIs.
Authoring contract
<Fetch
url="https://api.github.com/repos/taras/executable.md/pulls/181/reviews"
headers={{ Accept: "application/vnd.github+json" }}
as="response"
/>The initial component accepts:
| Prop | Required | Contract |
|---|---|---|
url |
yes | An absolute http: or https: URL |
method |
no | GET by default; GET and HEAD are the only initial methods |
headers |
no | String-valued request headers; names normalize to lowercase before the request; two supplied names that normalize to the same key are refused before the request begins; secret-bearing values are not a credential mechanism |
timeout |
no | A positive finite duration, parsed by the existing duration grammar such as 30s, overriding the contextual Fetch default |
as |
no | The ordinary engine-owned result binding |
A settled response is JSON:
{
"status": 200,
"headers": {
"content-type": "application/json"
},
"body": "..."
}Response header names are lowercase. The response header object is retained and compared in lexicographic header-name order. Each value is the complete string value reported by the runtime adapter for that case-insensitive header after the adapter's own combination rules. A test provider that supplies repeated values before normalization joins them in provider order with , , so repeated values have one deterministic component-level representation.
A HEAD response binds an empty body. The initial body is UTF-8 text; JSON parsing remains the job of <Parse> or <SafeParse>.
The runtime Fetch adapter must expose enough detached header data to construct this value consistently. A live host Headers object or response handle never enters a binding or journal.
Status and binding
With as, every received HTTP status is data. A 404, 409, or 500 binds the same response shape as a 200 and does not fail merely because of its status.
Without as, a non-2xx status fails the component with an HTTP-status error and a 2xx response completes successfully. The successful response is a non-string value and therefore renders nothing under the ordinary uncaptured-value rule.
Transport failure, invalid URL, timeout, cancellation, body-read failure, or failure before a complete response exists always fails and binds nothing. Binding never converts those failures into data.
Retention and replay
The host selects retention independently of display or binding:
- Ordinary
xmd runwithout--journalperforms the request live. A bound response exists only for the current execution and is not accumulated durably. - Diagnostic
xmd run --journalretains the normalized request and complete response under the diagnostic journal's existing policy. xmd workflowretains the normalized request and complete response as the durable effect result. Replay restores that exact response without repeating the request.- Binding does not itself request retention, and observing or suppressing rendered output does not alter the retained record.
If interruption occurs before the response is durably committed, a GET or HEAD may run again. The initial method restriction makes that retry a read rather than silently claiming reconciliation for a mutating external effect. POST, PUT, PATCH, DELETE and request bodies require a separate external-effect contract with stable identity and uncertain-completion reconciliation.
Every retained request and response crosses the existing secret gate before persistence. A refusal retains neither the offending event nor matched material.
Credentials and destination policy
Raw credential values are not the public authentication model:
- a secret supplied in authored headers is subject to the secret gate and cannot be relied on for a retained workflow;
- the component does not expose environment variables, host tokens or credential storage;
- host-owned credential references and injection may be added separately once their authority, redaction and provider contracts are settled.
<Fetch> delegates the live request through contextual API.Fetch, preserving test substitution, timeouts and cancellation. A trusted host may narrow reachable destinations. Public middleware or a document cannot widen a host ceiling.
Fetch is an ordinary core default for authored documents. A repository component named Fetch may shadow the default like any other component, but that shadow is not the core Fetch identity and does not satisfy generated-XMD admission. The host destination ceiling is enforced by the contextual API.Fetch provider below the component; public middleware, props, eval bindings, generated source, or a same-name repository component may narrow or refuse, but cannot widen that host ceiling.
Allowing a pinned <Fetch> identity in generated XMD is a separate #369 decision and does not grant arbitrary network access. #369 must authorize the request against explicit constraints such as allowed scheme, host, path/method shape and permitted headers before the first generated effect runs. A fragment outside those constraints is rejected with no request.
Lifecycle
The request and body read belong to the component invocation's Effection scope. Cancellation aborts them and teardown completes before the invocation closes. The component creates no detached Promise, process, service or response-body resource.
Deno, Node, Bun and the compiled binary use their shipped runtime adapters and provide the same observable component contract.
Acceptance
<Fetch url="..." as="response" />binds the normalized status, detached headers and UTF-8 body from contextualAPI.Fetch.- Request header names normalize to lowercase; duplicate supplied names after normalization are refused before a request begins.
- Response header names normalize to lowercase, sort deterministically for retention/comparison, and represent repeated provider values with the specified joined string.
- GET is the default; GET and HEAD are accepted; mutating methods and request bodies are refused before a request begins.
timeoutaccepts a positive finite duration using the existing duration grammar and refuses invalid, zero, negative, infinite or unknown units before a request begins.- With
as, non-2xx status is data; withoutas, non-2xx fails and later executable work does not begin under ordinary failure policy. - Transport, timeout, cancellation and body-read failures bind nothing.
- A successful unbound response renders nothing.
- Run without a journal retains no response beyond the live execution; diagnostic run and workflow retention follow their respective host policies.
- Workflow replay restores the exact response without a second request.
- An interruption before durable commit may repeat only the admitted GET/HEAD read.
- Retained URL, request headers, status, response headers and body cross the secret gate.
- Response handles and host-specific header objects never cross the component or journal boundary.
- A host destination ceiling cannot be widened by public middleware, a same-name repository component, props, eval bindings or generated source.
- This story does not change generated-XMD admission: generated fragments still cannot use
Fetchuntil Evaluate Agent-generated XMD through a constrained allowlist #369 admits the pinned component identity and exact bounded request. - Concurrent and nested executions keep providers, responses and cancellation independent.
- Focused Deno, Node, Bun and compiled regressions prove the same contract, with platform-adapter coverage where behavior differs.
- Architecture and the executable-MDX specification describe Fetch authority, retention, status handling, failure, timeout and replay in present tense.
Deterministic regressions
Freeze focused coverage for:
- GET default and explicit HEAD;
- detached status, normalized sorted headers and body;
- duplicate request-header refusal after case-insensitive normalization;
- timeout duration acceptance and invalid timeout refusal;
- bound 2xx and bound non-2xx;
- unbound 2xx and failing unbound non-2xx;
- refusal of POST, PUT, PATCH, DELETE and a request body before the server is reached;
- contextual Fetch substitution and host destination refusal;
- run transient behavior, diagnostic retention and workflow replay without a second request;
- interruption before commit and one repeated safe read;
- request/response secret refusal with no offending append;
- timeout, cancellation and body-read failure with no binding;
- same-name component and public middleware failing to acquire or widen host authority; and
- generated-XMD admission remaining unchanged, with generated
Fetchstill refused because Evaluate Agent-generated XMD through a constrained allowlist #369 owns later admission of the pinned identity and bounded request.
Tests must observe request count separately from bound result and journal contents. Matching rendered text is not evidence of retention or replay.
Documentation and compatibility
Add Fetch to the architecture terminology registry as an XMD-mediated HTTP read, distinct from an Agent's own network authority and from reconciled mutating external effects. Update:
- the runtime/contextual API and authority sections of
architecture.md; - the executable-MDX component, failure, timeout, retention and tier sections;
- workflow security and generated-XMD guidance;
- the testing/mocking guidance for
API.Fetch; and - PR ✨ Compose and certify the supervised adversarial implementation workflow #181's living target when the component ships.
Fetch is an ordinary core default for authored documents. A same-name repository component can shadow the default for authored source, but it cannot impersonate the core identity for generated-XMD admission and cannot widen the trusted host's destination ceiling. The host ceiling itself remains below replaceable public policy.
This is additive. Existing eval fetch, API.Fetch, timeout options and provider test seams remain compatible.
Dependencies and related work
- Existing contextual
API.Fetchis the live transport seam. - Reject secrets before journal persistence by default #199 owns the existing persistence secret boundary.
- Evaluate Agent-generated XMD through a constrained allowlist #369 owns later admission of constrained Agent-generated XMD for the pinned core
Fetchidentity and exact bounded requests. It is not a prerequisite for this authored-component implementation slice. - Run workflow Agents through XMD-mediated observations #302 keeps the Agent process itself read-only and network-denied.
- ✨ Compose and certify the supervised adversarial implementation workflow #181 is the motivating workflow target.
Out of scope
- Mutating HTTP methods or request bodies.
- Automatic JSON parsing.
- Pagination, retries based on application status, caching or rate-limit policy.
- A Git-host-specific reviews/comments/checks component.
- Raw access to host credentials or environment variables.
- Streaming response bodies, uploads, multipart forms, WebSockets, SSE or interactive protocols.
- Changing the Agent's native network ceiling.
- Generated-XMD admission of
Fetch; Evaluate Agent-generated XMD through a constrained allowlist #369 owns that later proof and implementation.
Activity
📝 Separate the shipped suspension substrate from what this Markdown c…
📝 Separate the shipped suspension substrate from what this Markdown c…
📝 Separate the shipped suspension substrate from what this Markdown c…
📝 Separate the shipped suspension substrate from what this Markdown c…
📝 Separate the shipped suspension substrate from what this Markdown c…
📝 Separate the shipped suspension substrate from what this Markdown c…
📝 Separate the shipped suspension substrate from what this Markdown c…
📝 Separate the shipped suspension substrate from what this Markdown c…
📝 Separate the shipped suspension substrate from what this Markdown c…
📝 Separate the shipped suspension substrate from what this Markdown c…
📝 Separate the shipped suspension substrate from what this Markdown c…
📝 Separate the shipped suspension substrate from what this Markdown c…