Zum Hauptinhalt springen
Markdown anzeigen

Execution Contexts

The same iXML file can behave differently depending on how it was started. Identity, available globals, and HTTP behavior all come from the execution context, not from the file. Most app bugs that survive compilation are context bugs: code that works in zeysdk run fails in a weblet, or a service that works with a token rejects a browser call.

Identify the context first, then read the row that applies. Test in that same context. A pass in one context says little about another.

How to read the evidence labels​

LabelMeaning
ReferenceStated in the official iXML, SDK, or API reference
Source-confirmedRead in the platform or SDK source for this release. It describes behavior at contract level and has not been executed for this page. A later platform release can change it
LoggedDated execution result in the guide's validation log
ReportedField observation that was not independently verified. Check it in your target

Context matrix​

ContextIdentity and auth sourceInputs and globalsOutput and HTTPSide effectsHow to test it
REST API (external client, for contrast)Token sent by the client, or the session cookie of a signed-in browserThe documented request only. Your iXML does not run hereJSON as defined by the OpenAPI referenceRecord writes can fire entity hooksHTTP request against a sandbox, or the zeyos CLI
Weblet, framed or pageBrowser session cookie. No valid session means no executionREQUEST, GET, POST, FILES, SERVER, SELFURL, VIEW, SELFID, SELFNS, APPSETTINGS. Record context arrives as request parameters such as $REQUEST.idYour output and headers go to the frameNo implicit transaction. Use db:transactionOpen the real view while signed in
Weblet in a .form viewRuns while the page is built for the signed-in userENTITY, ID, DATA, EXTDATA, TAGS, FORM, SELFID, SELFNS, APPSETTINGS. No REQUEST. New-record dialogs differ: no FORM, and normally no record variablesForm controls are collected. Plain output is discardedForm data is saved by the host pageOpen the record form and save it
REMOTECALL serviceBearer token of the access or legacy type, read into AUTH. The cookie is ignored. A configured access key is a separate branchAUTH when authenticated, REQUEST, GET, POST, FILES, SERVER, SELFURL, SELFID, SELFNS, APPSETTINGS. Inside rest:resource: route parameters, plus query arguments or POST form fields as locals, which can hide these globalsYour output, headers, and status go to the caller. CORS is openNo implicit transaction. Unauthenticated calls can still run, even for a restricted applicationHTTP request with a token, without one, with an invalid one, and with forged query arguments
zeysdk runSDK token of a developer. No end-user sessionSELFID, SELFNS, APPSETTINGS, DEBUG. No AUTH, REQUEST, SELFURL, or VIEWOutput, status, and headers are returned as a reportReal. Writes, mail, and external calls all happenOnly for the code path itself, on a sandbox
TIMING serviceScheduler. No userSELFID, SELFNS, APPSETTINGSDiscarded. Errors go to the platform logNo implicit transaction. One concurrent run per serviceWait for the schedule and inspect the effects
Entity hookRuns inside the operation that changed the record. No AUTHENTITY, ID, and event-specific values, plus SELFID, SELFNS, APPSETTINGSDiscardedInside the record transaction. An error aborts the operationTrigger the real entity event

The globals, auth branches, and output handling in this table are Source-confirmed. Asset types and the REMOTECALL and TIMING metadata are Reference.

APPSETTINGS is the one input every application context shares. Everything else must be checked per context. The global names above are the iXML forms. Zymba has its own access syntax, so check ref/zymbadoc.md for Zymba assets.

Weblets​

A weblet's type decides how the browser reaches it. Its view decides where it appears. ZeySDK 3.3.0 accepts these types:

TypePresentation (Source-confirmed)
INTEGRATEDMenu entry that opens a page inside the ZeyOS shell
STANDALONEMenu entry that navigates the same tab
DETACHEDMenu entry that opens a new tab
POPUP_FRAMED, POPUP_PLAINMenu entry that opens a dialog with a frame
EMBEDDED_FRAMED, EMBEDDED_COLLAPSED, EMBEDDED_PLAINFrame inside the view, with or without a collapsible heading

All of these reach the same weblet endpoint, with one exception: a weblet registered on a view ending in .form is not a page. ZeyOS runs it on the server while it builds the form, so it must be iXML and it gets the record variables directly. Choose the path before you write code, because $REQUEST and $ID are not interchangeable across the two.

The endpoint requires a valid session. Without one it answers 401. An inactive weblet or application answers 403, as does a restricted application the user has no permission for. A weblet with a configured url redirects to that URL instead of running code.

view controls placement, not access. A weblet's URL is reachable by any signed-in user, regardless of its view, unless the application is restricted and the user lacks permission for it.

$REQUEST.id is an ordinary query parameter. The session proves who the user is, not that this user may see that record. Check access before you use the value in a query. See Weblets for an example and for the limits on requests that change data.

REMOTECALL services​

The service URL has this form, where <base> is your instance URL:

Text
<base>/remotecall/<app>.<service>/<route>

$SELFURL holds the numeric form of the same address, <base>/remotecall/~<ID>.

These are the corrections most often needed (Source-confirmed, with the SDK access-key flag as Reference):

  • The browser session cookie does not authenticate a REMOTECALL call. The platform's auth step reads only the Authorization header.
  • A valid token populates AUTH. It holds user, application, and lang. Access tokens and legacy tokens are accepted. SDK and API tokens are not.
  • AUTH.user means any valid token. It does not show which application the token was issued to. Compare AUTH.application where that matters.
  • An unauthenticated call can still execute. A missing token and an invalid token both leave AUTH unset, and your script runs anyway. The platform does not answer 401 for you. Every protected handler must authorize explicitly. A restricted application does not change this: its permission check applies only when a user was identified, so an anonymous call still runs.
  • An access key is a separate branch. If the service has an accesskey, the platform rejects a call without the key with 401. On success AUTH holds the key, not a user, so $AUTH.user is empty.
  • CORS is not authorization. Every REMOTECALL response sends Access-Control-Allow-Origin: *. That header controls which pages may read a response. It does not restrict who can call the service.
  • A JSON body does not fill REQUEST. Read it with var_body on rest:resource and decode it inside try.

Bind globals inside rest:resource​

Inside a handler, the request can create local variables, and locals hide globals (Reference). Query arguments become locals. For POST, form fields do instead (Source-confirmed). A global you read without binding it is therefore under the caller's control.

This matters most for authorization. Here ?AUTH[user]=1 passes the check with no token:

XML
<!-- Wrong: a query argument can supply AUTH -->
<rest:resource route="/greeting/:name" method="GET">
<is var="AUTH.user" type="empty">
<set var="return">401</set>
<return />
</is>
</rest:resource>

<!-- Right: AUTH now refers to the platform value -->
<rest:resource route="/greeting/:name" method="GET">
<use var="AUTH" />

<is var="AUTH.user" type="empty">
<set var="return">401</set>
<return />
</is>
</rest:resource>
warning

The general rule: inside rest:resource, any platform global (AUTH, APPSETTINGS, REQUEST, and so on) can be replaced by the request. Bind each one with use as the first child, before reading it. Do the same for your own globals, including resource controllers, macros, and shared arrays.

  • A check before rest:server is not sufficient. It reads the real AUTH, but $AUTH.* inside the handler can still be forged, for example $AUTH.lang.
  • Functions are not affected. A function that the handler calls has its own context, so $APPSETTINGS inside a resource function reads the real global.
  • Route parameters win over query arguments of the same name.
  • Any local you read before assigning it is also caller-controlled. Assign your locals first.

This behavior was reproduced in a local run of the iXML engine with stand-in globals. Add a forged-argument test to every protected service. See Checkpoint 4.

warning

The session command reads the cookie on its own, independently of the auth step. If a browser sends the cookie to a REMOTECALL URL, session may return that user while AUTH stays unset. Do not build authorization on this. It has not been verified at runtime, it skips the restricted-application check, and a cookie-authorized service is exposed to cross-site request forgery.

Calling your own service from a weblet​

The browser may send its cookie with the call, but the auth step ignores it, so a weblet frame that calls its own REMOTECALL service arrives unauthenticated. Do not paper over this by accepting anonymous calls or by shipping a long-lived token to the browser.

First ask whether the browser needs to call a service at all. The weblet already runs server-side in the user's session and can call the same resource directly, as the minimal app does.

If the browser must call the service, that is a security design and it needs review. This guide does not publish a recipe. The design must define:

  • Principal binding: which user the credential represents, set on the server from the session, never from a request parameter.
  • Route binding: which routes, methods, and records it permits.
  • Expiry: a short lifetime, checked on the server.
  • Replay policy: whether reuse is allowed and how it is detected or limited.
  • Server-side key handling: any signing key stays in instance settings and never reaches the page, the logs, or a response.
  • Verification: tests for a missing, expired, tampered, wrong-user, and wrong-route credential, each run through a real HTTP request.

zeysdk run​

zeysdk run is console execution with application globals. It is not a simulated weblet and not an inbound HTTP request.

  • It sends your local file to the instance and executes it there, so it can run code you have not pushed. Anything that file includes resolves to the deployed assets on that instance.
  • It runs with the app's namespace and settings, so $APPSETTINGS and .name includes work.
  • There is no session, no AUTH, no REQUEST, no SELFURL, and no VIEW. session returns NULL.
  • Console requests use PUT, so GET and POST routes in rest:server do not match. Logged: on 6 February 2026, an inline REST example returned HTTP 404 and null.
  • Effects are real. The console has no dry-run mode.

Use run to check a function or a query shape. Do not use it to conclude that a weblet, service route, or hook works. For compile-only checks, use zeysdk compile. See Verification Checkpoints.

TIMING services​

The scheduler calls the service by schedule and interval. schedule is the start minute of the day, from 0 to 1439, and interval is the repeat period in minutes. With an interval of 1, schedule is ignored. The platform prevents concurrent runs of one service. All of this is Source-confirmed. Older ref/sdk pages show a 0 to 2359 time format, which the current SDK rejects. A skipped run is logged, not queued, so a job must be able to resume on its next run.

There is no user and no request. Log what the job did, because output is discarded.

Entity hooks​

Hook types are AFTER_CREATION, BEFORE_MODIFICATION, AFTER_MODIFICATION, AFTER_CREATION_MODIFICATION, BEFORE_DELETION, and AFTER_DELETION. Each needs an entity (Reference).

Inputs depend on the event (Source-confirmed):

EventInputs besides ENTITY and ID
CreationNEW, NEW_EXTDATA, NEW_TAGS, MOD, MOD_EXTDATA, MOD_TAGS_ADD. No OLD
ModificationAll of the above, plus OLD, OLD_EXTDATA, OLD_TAGS, MOD_TAGS_REMOVE
DeletionOLD, OLD_EXTDATA, OLD_TAGS only

AFTER_CREATION_MODIFICATION runs for both creation and modification, so check OLD before using it.

A hook runs inside the transaction of the change that triggered it (Source-confirmed). This has three consequences:

  • An uncaught error aborts the user's operation, including from an AFTER_* hook.
  • Slow work in a hook slows every save of that entity. Move heavy work to a TIMING service.
  • External side effects such as mail or HTTP calls are not rolled back with the transaction.

While a modification hook runs, a further change to the same record does not fire modification hooks again (Source-confirmed).

Includes and JSON resources​

include loads a resource by default. Use type to load a service or a weblet (Reference):

XML
<include id=".Greeting" />
<include id=".api" type="service" />
<include id=".hello" type="weblet" />

The leading dot addresses an asset in the current app. It expands to the app identifier, so .Greeting in example.minimal means example.minimal.Greeting. This form is Source-confirmed and used throughout real apps, but ref/ixmldoc.xml does not describe it. It fails in a script that has no app namespace.

What a resource returns depends on its MIME type, not its extension (Source-confirmed):

Resource MIME typeinclude returns
application/ixml+xml, text/x-zymbaThe executed code's result
application/jsonDecoded value
application/xml, text/xmlParsed tree
text/csv, text/tab-separated-valuesParsed rows
Other text typesText, with $name replaced from the include's parameters

The SDK derives the MIME type from the extension when you add a file, which is why the two usually agree.

A JSON resource is already decoded, so decode:json on it is a mistake:

XML
<!-- Wrong: the value is already an array -->
<include id=".defaults" var="raw" />
<decode:json var="defaults">$raw</decode:json>

<!-- Right -->
<include id=".defaults" var="defaults" />
<output>$defaults.title</output>

Session locale​

The session command returns an array, or NULL when there is no session (Reference). The reference does not list its keys.

The user's locale is in data.lang (Source-confirmed, and Reported from an app build in November 2026). A top-level lang key is not part of the contract, and ref/dbref.json lists no lang column on users.

XML
<session var="session" />
<set var="lang">$session.data.lang</set>

In a REMOTECALL service, use $AUTH.lang. Always handle an empty value.

session returns NULL only when there is neither a session user nor a valid cookie. That makes the result context-dependent: a hook fired from a signed-in page sees that user, while the same hook fired by a TIMING service does not. Do not rely on session in hooks or in REMOTECALL.

Common mistakes​

  • Treating a zeysdk run pass as proof that a weblet or service route works.
  • Assuming a REMOTECALL handler is protected because the platform did not return 401.
  • Reading $AUTH or $APPSETTINGS inside rest:resource without binding it with use.
  • Treating view as an access control.
  • Reading $REQUEST.id in a .form weblet, or $ID in a framed weblet.
  • Sending a user-specific token to the browser to work around the missing session.
  • Doing slow or external work inside a BEFORE_* hook.

Evidence​

ItemRecord
Guide sourceRevision 5271871 plus the working-tree changes of this update
Referencesref/ixmldoc.xml, ref/sdk/, ref/dbref.json, ref/openapi/sdk.json, LEARN.md
SDK inspectedzeysdk 3.3.0, source and offline help
CheckedStatic review against the references, reading the platform and SDK source, and running the rest:resource binding examples in a local copy of the iXML engine with stand-in globals
Not performedRemote compilation, deployment, and execution on an instance. Apart from the dated log entry it cites and the local engine run, this page's claims have not been executed

Open runtime checks are listed in contributing/REVIEW_BACKLOG.md.