Verification Checkpoints
"It compiles" and "it works" are different claims. This page splits verification into four checkpoints, each with its own expected outcome. A pass at one checkpoint says nothing about the next. Record which ones you actually ran.
| Checkpoint | Needs | Proves | Does not prove |
|---|---|---|---|
| 1. Local inspection | Your files | Well-formed XML, names checked against the reference, consistent manifest | That the native compiler accepts the code |
| 2. Remote compilation | Linked app, network, developer permission | The instance's compiler accepts the checked files | Any runtime behavior |
| 3. Deployment | Same, plus permission to change the instance | The instance holds the reviewed change set | That the code works in its context |
| 4. Runtime verification | A sandbox and the real entry point | Observed behavior for the inputs you tried | Other inputs and production conditions |
Checkpoint 1: local inspection
Nothing leaves your machine at this checkpoint.
XML well-formedness. This check is partial. It finds broken XML and nothing else.
xmllint --noout --nonet resources/Greeting.ixml
Expected outcome: exit status 0. xmllint also prints Namespace prefix ... is not defined for commands such as rest:server, because iXML prefixes are not declared XML namespaces. These messages are expected. A parser error such as an unescaped < in an attribute is a real failure.
| Failure case | Result |
|---|---|
Raw < in an attribute | xmllint fails. Escape it as < |
<if condition="..."> | xmllint passes. The native parser rejects it, as logged on 6 February 2026. This is why the reference check below is required |
Invented command such as array:count | xmllint passes. The native parser rejects it, as logged on 6 February 2026 |
Reference lookup. Look up every changed command in ref/ixmldoc.xml, including attributes, children, and result bindings. Check database identifiers in ref/dbref.json and weblet views in ref/sdk/views.md. Coding Agents and Validation has the full checklist.
Manifest and asset consistency. For each manifest entry, confirm that:
- The file exists at
<class>/<key>.<extension>, and the extension fits itsmimetype. - The
typeis on the SDK's list and uses the SDK's uppercase spelling. - Hook services have an
entity, andTIMINGservices have ascheduleandinterval. - Each weblet's
viewexists inref/sdk/views.md. - Every
includetarget exists in the same manifest, with the righttypeselector. - Every
rest:resourcebinds each global it reads withuseas its first child. See Bind globals insiderest:resource. - No secret appears in
defaultsettings.jsonor any tracked file.
What zeysdk checks offline. zeysdk --help, zeysdk <command> --help, and zeysdk version work without a link. Local arguments are validated before a remote call, so zeysdk add and zeysdk update reject unknown types and unsafe paths. No SDK command compiles code offline.
Checkpoint 2: remote compilation
zeysdk compile and zeysdk lint send your source to the linked instance's compiler and report the result. They do not execute the code, but they are not offline. They need a linked app, a valid token, developer permission, and network access. Confirm the instance is the one you intend, and that you may send it this source.
zeysdk whoami
zeysdk compile services/api.ixml
zeysdk compile --ixml '<output>Hello</output>'
zeysdk compile --zy 'return 1;'
zeysdk lint
zeysdk lint --all
compile takes a path or one inline source. --zymba is the long form of --zy. lint compiles the eligible scripts the app knows about. By default it selects only locally present scripts that differ from the last synchronized state. --all selects every locally present script, skipping the server comparison, but it still sends source.
Expected outcome: compile reports success, and lint prints OK for each file you meant to check, with a summary count. Record the file list. On failure both commands print the compiler message and exit nonzero.
| Failure case | Meaning |
|---|---|
Lint prints No modified iXML or Zymba files to lint. or No iXML or Zymba files found. | Not a pass. Zero files were checked, and the exit status is still 0 |
| A script is missing from the lint list | It is not registered in the manifest. Register it, or compile it by path |
requires the zeysdk.sync.json file or missing id/url | The app is not linked. Run zeysdk link |
401 or Lacking developer permission | Fix the token or the user's permission. This is not a code result |
What compile cannot prove. It checks the code you sent, on that instance's compiler. It does not show that database identifiers exist, that a handler's auth check cannot be forged, that an include target is deployed, that a settings key is present, that inputs are complete, that authorization is correct, or that the page renders. Runtime errors in dynamic values appear only at checkpoint 4.
Checkpoint 3: deployment
push changes the instance. It does more than upload: a whole-project push makes the server match your working copy.
zeysdk whoami
zeysdk compare
zeysdk snapshot create "Before push"
zeysdk push --dry-run
zeysdk push
Expected outcome: the dry run names the application, instance, and URL, and lists every upload and every server deletion. You can explain each line. The real push then applies that plan, and a following zeysdk compare shows no unexpected drift.
- Scope.
zeysdk push services/api.ixmlplans only the named paths. Without paths the plan covers the whole project, including deletions. - Deletions. A service, weblet, resource, or asset that exists on the server but not locally is deleted, even if this checkout never synchronized it.
--keep-remoteprotects remote-only assets that have no sync baseline in this checkout. It does not protect every missing local asset. If this checkout synchronized an asset and you then removed it locally, a push still deletes it from the server.--forceskips the confirmation prompt. It does not grant authorization, and it does not make the plan safer. Use it only after reviewing the same change set.- Dry run limits. A dry run reads the server and may make read-only requests. The server does not lock, so a change made after the preview is not part of what you reviewed.
- Snapshots are local ZIP archives of project files. They restore your working copy, not the instance.
Neither a snapshot nor a sync.json backup rolls back the server. Plan recovery before you push to a shared instance.
| Failure case | Meaning |
|---|---|
| The plan deletes something you did not touch | Another developer's work, or a stale checkout. Stop and run zeysdk compare. Consider --keep-remote or a targeted push |
| The plan targets the wrong instance | Stop. Run zeysdk use <instance> |
| A push fails partway | Local recovery restores sync state only. Compare before retrying |
Checkpoint 4: runtime verification
Test through the real entry point, on a sandbox, with bounded inputs. See Execution Contexts for why zeysdk run does not substitute for these.
| Context | How to test | Expected outcome | Failure cases to try |
|---|---|---|---|
| Weblet | Open the target view in a signed-in browser. Inspect the frame request in developer tools | Frame status 200 and the expected content | Signed-out user gets 401. User without access to a restricted app gets 403. Empty and hostile $REQUEST values stay safe |
REMOTECALL | Send HTTP requests with curl | Valid access token gets 200 and the expected body | No token, an invalid token, an SDK token, and no token with a forged ?AUTH[user]=1 must all be rejected by your handler. A real token with forged arguments must keep its real identity. A 200 on any rejected case is a security failure |
TIMING | Let the schedule fire on a sandbox, then inspect the data and log | One run, with expected effects | A rerun does not repeat completed work. A failed run is logged and the next run recovers |
| Entity hook | Create, change, or delete a real record through the UI or API | The expected change, and only for the intended records | A thrown error aborts the save. Non-matching records are untouched |
The three HTTP checks every protected service needs:
# Expected: 200
curl -i -H "Authorization: Bearer $ZEYOS_ACCESS_TOKEN" \
"https://cloud.zeyos.com/<instance>/remotecall/example.minimal.api/greeting/Ada"
# Expected: 401 from your handler
curl -i \
"https://cloud.zeyos.com/<instance>/remotecall/example.minimal.api/greeting/Ada"
# Expected: 401. A 200 means a request can forge AUTH.
curl -i -g \
"https://cloud.zeyos.com/<instance>/remotecall/example.minimal.api/greeting/Ada?AUTH[user]=1"
-g stops curl from treating the brackets as a pattern. For a POST route, send the same field in the form body.
Timeouts and uncertain completion
SDK requests time out after 30 seconds by default. Set ZEYSDK_REQUEST_TIMEOUT_MS to a positive integer to change this. The limit applies to the client's wait.
A disconnected client does not prove the server stopped. The script may still be running, or may already have finished. The SDK's request layer does not automatically resend a run, compile, or push request, and retries only reads. zeysdk watch is the exception: it keeps a failed batch and pushes it again with backoff, so stop it before you investigate. Outside watch, any repeat comes from you.
Before you retry after a timeout:
- Treat the outcome as unknown, not failed.
- Check the effects: records, sent mail, external calls, and the platform log.
- Retry only what did not happen, and only if the operation is safe to repeat.
For work that may take longer, use a TIMING service with a resumable, time-bounded batch instead of raising the timeout.
Record what you ran
Keep the date, revision, instance, checked file list, and result for each checkpoint. Mark any checkpoint you skipped as not performed. Never describe compiled code as runtime-validated.