# The ctx reference

Normal and Integration scripts reach the platform through `ctx`; Pure scripts have none. A function shown returning `ok, result` returns two values: when `ok` is true, `result` is the value shown; when it is false, `result` is the error message. Functions marked *Integration only* are available only in integrations; every other function is available in both profiles.

## ctx.cache

- `ctx.cache.delete(key: string) -> ok, result: nil`\
  Deletes a cache value: your account's cache, or in an integration the instance's.

- `ctx.cache.get(key: string) -> ok, result: any`\
  Reads a cache value: your account's cache, or in an integration the instance's.

- `ctx.cache.set(input: { key: string, ttl: number | nil, value: any }) -> ok, result: nil`\
  Writes a cache value (single-table input): your account's cache, or in an integration the instance's. ttl is in seconds (default 3600, capped at 604800 = 7 days); the encoded value is limited to 4 KB.

## ctx.config

- `ctx.config.get(key?: string, default?: any) -> ok, result: any`\
  Reads a config value, with an optional default: your account's config, or in an integration the instance's install-time config. With no key, returns the whole config table.

## ctx.dataContainer

The older name for `ctx.store`: `ctx.dataContainer.delete`, `ctx.dataContainer.get`, `ctx.dataContainer.list`, `ctx.dataContainer.put` and `ctx.dataContainer.query` work the same way as their `ctx.store` counterparts.

## ctx.device

- `ctx.device.forWorkcenter(workCenterId: string) -> WorkCenterDevice`\
  The device assigned to this work center. Not available in integrations.

- `ctx.device.get(idOrName: string) -> Device`\
  The device with this id or name. In an integration, the id of one of the resources its manifest binds.

- `ctx.device.http(idOrName: string, request: { auth: { basic: { password: string, username: string } | nil, bearer: string | nil } | nil, body: string | nil, headers: { [key: string]: string } | nil, method: string | nil, path: string | nil, query: { [key: string]: any } | nil, timeoutMs: number | nil, url: string | nil }) -> ok, result: { body: string | nil, headers: { [key: string]: string }, status: number }`\
  Sandboxed HTTP request through a device connection. The request body must be a string (json.encode a table first); the response body is the raw string, never decoded (json.decode it).

- `ctx.device.sql(idOrName: string, request: { bindings: any[] | nil, query: string }) -> ok, result: { columns: string[], executionTimeMs: number | nil, rows: any[][], rowsAffected: number | nil }`\
  Sandboxed SQL through a device connection. Edge transport envelopes, including SQL results, are limited to 16 MiB (16,777,216 bytes, including protocol overhead). Select only needed columns and page large result sets.

## ctx.edgeApp

- `ctx.edgeApp.invoke(handle: string, payload?: any) -> ok, result: any`\
  Invokes a deployed edgeApp by handle.

## ctx.graph

- `ctx.graph.impact(entityId: string, options?: { direction: string | nil, maxDepth: number | nil }) -> ok, result: { depth: number | nil, id: string, label: string | nil, status: string | nil, type: string }[]`\
  Returns entities reachable up/downstream from a fulfillment entity.

- `ctx.graph.provenanceImpact(entityId: string, options?: { direction: string | nil, maxDepth: number | nil }) -> ok, result: { depth: number | nil, id: string, label: string | nil, status: string | nil, type: string }[]`\
  Returns entities reachable up/downstream from a provenance entity, over the structural composite (operational + fulfillment + provenance). Requires provenance:read.

- `ctx.graph.provenanceTrace(entityId: string, options?: { direction: string | nil, maxDepth: number | nil }) -> ok, result: { edges: { rel: string, source: string, target: string }[], nodes: { depth: number | nil, id: string, label: string | nil, status: string | nil, type: string }[] }`\
  Returns the cross-layer lineage subgraph (nodes + edges) around a provenance entity, over the structural composite (operational + fulfillment + provenance). Requires provenance:read.

- `ctx.graph.trace(entityId: string, options?: { direction: string | nil, maxDepth: number | nil }) -> ok, result: { edges: { rel: string, source: string, target: string }[], nodes: { depth: number | nil, id: string, label: string | nil, status: string | nil, type: string }[] }`\
  Returns the fulfillment lineage subgraph (nodes + edges) around an entity.

- `ctx.graph.whatIf(seeds: string[], extraEdges: { rel: string | nil, source: string, target: string, weight: number | nil }[], options?: { direction: string | nil, maxDepth: number | nil }) -> ok, result: { affected: { depth: number | nil, id: string, label: string | nil, newlyReachable: boolean, status: string | nil, type: string }[], introducesCycle: boolean }`\
  First-class hypothetical impact: the delta between the baseline reachable set from seeds and the reachable set with extraEdges unioned in (over the structural composite by default), plus whether the injected edges introduce a cycle. Requires provenance:read.

## ctx.graphql

- `ctx.graphql.query(query: string, variables?: { [key: string]: any }) -> ok, result: { data: any | nil, errors: { message: string }[] | nil }`\
  Runs a GraphQL operation with the caller's authorization. Validation infers selected result fields and variable types for literal operations and immutable query strings; dynamic documents retain the general result envelope.

## ctx.integration

- `ctx.integration.getBindings() -> ok, result: { [key: string]: string }` *Integration only*\
  The manifest resource bindings of this instance.

- `ctx.integration.getId() -> ok, result: string` *Integration only*\
  The integration instance ID.

- `ctx.integration.getIntegrationId() -> ok, result: string` *Integration only*\
  The integration definition ID.

- `ctx.integration.getVersion() -> ok, result: string` *Integration only*\
  The installed integration version.

## ctx.jdf

- `ctx.jdf.build(ops: any[]) -> ok, result: string | nil`\
  Renders a JDF build-operation list to JDF XML. Returns (ok, xml) — ok=false with the error message on failure.

- `ctx.jdf.loadTemplate(name: string) -> ok, result: any`\
  Loads a stored JDF template script by name and returns the value that script returns.

- `ctx.jdf.parse(xml: string, options?: any) -> ok, result: any | nil`\
  Parses JDF XML into a table. Returns (ok, doc) — ok=false with the error message on failure.

## ctx.jmf

- `ctx.jmf.build(ops: any[]) -> ok, result: string | nil`\
  Renders a JMF build-operation list to JMF XML. Returns (ok, xml) — ok=false with the error message on failure.

- `ctx.jmf.loadTemplate(name: string) -> ok, result: any`\
  Loads a stored JMF template script by name and returns the value that script returns.

- `ctx.jmf.parse(xml: string, options?: any) -> ok, result: any | nil`\
  Parses JMF XML into a table (kind = jmf or jdf_stub). Returns (ok, doc) — ok=false with the error message on failure.

## ctx.knowledge

- `ctx.knowledge.query(seeds: string[], options?: { limit: number | nil, query: string | nil }) -> ok, result: { graph: { body: string, conceptId: string | nil, confirmations: number, contradicts: string[], entityId: string | nil, entityType: string | nil, id: string, provenance: string, scope: string, score: number }[], supporting: { body: string, conceptId: string | nil, confirmations: number, contradicts: string[], entityId: string | nil, entityType: string | nil, id: string, provenance: string, scope: string, score: number }[] }`\
  Retrieves your account's knowledge relevant to the given bounded-entity seeds (TypeID strings; graph spread + attached notes, ranked). Optional free-text `query` biases the spread semantically.

## ctx.log

- `ctx.log.error(message: string, attrs?: { [key: string]: any }) -> nil`\
  Logs an ERROR message with optional structured attributes.

- `ctx.log.info(message: string, attrs?: { [key: string]: any }) -> nil`\
  Logs an INFO message with optional structured attributes.

- `ctx.log.warn(message: string, attrs?: { [key: string]: any }) -> nil`\
  Logs a WARNING message with optional structured attributes.

## ctx.ml

- `ctx.ml.clusterSeries(series: number[][], options?: { epsilon: number | nil, metric: string | nil, minClusterSize: number | nil, window: number | nil }) -> ok, result: { labels: number[], nClusters: number }`\
  DBSCAN clustering of series by DTW distance (augurs).

- `ctx.ml.detectChangepoints(series: number[], options?: { method: string | nil }) -> ok, result: { changepoints: number[] }`\
  Structural-break detection on a univariate series (augurs).

- `ctx.ml.detectOutliers(series: number[][], options?: { method: string | nil, sensitivity: number | nil }) -> ok, result: { isOutlier: boolean[], outlyingSeries: number[] }`\
  Outlier detection across a group of aligned series (augurs).

- `ctx.ml.detectSeasonality(series: number[]) -> ok, result: { periods: number[] }`\
  Periodogram seasonal-period detection on a univariate series (augurs).

- `ctx.ml.dtwDistance(a: number[], b: number[], options?: { metric: string | nil, window: number | nil }) -> ok, result: { distance: number }`\
  Dynamic-time-warping distance between two series (augurs).

- `ctx.ml.dtwDistanceMatrix(series: number[][], options?: { metric: string | nil, window: number | nil }) -> ok, result: { matrix: number[][] }`\
  Pairwise DTW distance matrix over a group of series (augurs).

- `ctx.ml.get(name: string) -> MLModel`\
  The model with this name.

- `ctx.ml.interpolate(series: number[]) -> ok, result: { values: number[] }`\
  Linear gap-fill of NaN holes in a series (augurs).

- `ctx.ml.predict(modelName: string, featuresJSON: string) -> ok, result: { confidence: number | nil, predictedClass: string | nil, prediction: number | nil, warnings: string[] | nil }`\
  Prediction against a model trained in your account.

- `ctx.ml.systemPredict(modelName: string, featuresJSON: string) -> ok, result: { confidence: number | nil, predictedClass: string | nil, prediction: number | nil, warnings: string[] | nil }`\
  Prediction against a system model role.

## ctx.notify

- `ctx.notify.send(input: { body: string, bodyHtml: string | nil, group: string | nil, recipient: string | nil, subject: string } | string, options?: { body: string, bodyHtml: string | nil, subject: string }) -> ok, result: { recipientCount: number, status: string } | nil`\
  Sends a notification to a group or recipient. Pass a single table { group?/recipient?, subject, body, bodyHtml? }. The legacy (targetId, options) two-arg form is deprecated.

## ctx.protobuf

- `ctx.protobuf.decode(descriptor: string, bytes: string) -> ok, result: any`\
  Decodes a protobuf payload (base64) against a descriptor: a registered one, or in an integration one its manifest declares.

## ctx.script

- `ctx.script.load(name: string) -> any`\
  Loads another stored Script by name and returns its exports. Raises a catchable error when the Script does not exist — wrap in pcall to fall back.

## ctx.sql

- `ctx.sql.query(sql: string, bindings?: { [key: string]: any }) -> ok, result: { headers: string[], limit: number, nextOffset: number | nil, rowCount: number, rows: any[][], truncated: boolean }`\
  Read-only SQL over your account's reporting tables. Maximum 1000 rows per query. Check truncated; use LIMIT and OFFSET with a stable unique ORDER BY to fetch subsequent pages using nextOffset, or aggregate in SQL.

## ctx.store

- `ctx.store.delete(name: string, schemaName?: string) -> ok, result: nil`\
  Deletes a stored record, if it exists.

- `ctx.store.get(name: string, schemaName?: string) -> ok, result: { createdAt: string | nil, data: any, name: string, schemaName: string | nil, updatedAt: string | nil } | nil`\
  Reads one stored record by name. In an integration, give the schema it belongs to.

- `ctx.store.list(schemaName?: string, opts?: { limit: number | nil, offset: number | nil }) -> ok, result: { createdAt: string | nil, data: any, name: string, schemaName: string | nil, updatedAt: string | nil }[]`\
  Lists stored records by name, up to opts.limit (default 100, at most 1000) from opts.offset.

- `ctx.store.put(name: string, data: any, schemaName?: string) -> ok, result: { createdAt: string | nil, data: any, name: string, schemaName: string | nil, updatedAt: string | nil }`\
  Creates or replaces a stored record. In an integration, give a schema the manifest declares.

- `ctx.store.query(query: { schemaName: string | nil, taggableId: string | nil, taggableType: string | nil }) -> ok, result: { createdAt: string | nil, data: any, name: string, schemaName: string | nil, updatedAt: string | nil }[]`\
  Finds stored records tagged with a job (taggableType = "job", taggableId), optionally in one schema.

## ctx.template

- `ctx.template.get(name: string) -> Template`\
  The template with this name. In an integration, the id of one of the resources its manifest binds.

- `ctx.template.render(template: string, vars?: { [key: string]: any }) -> ok, result: string`\
  Renders a stored template with the given variables: the one-call form of `ctx.template.get(name):render(vars)`.

## ctx.twin

- `ctx.twin.attributes() -> ok, result: { bands: { label: string, lowerInclusive: number | nil, upperExclusive: number | nil }[] | nil, description: string, displayName: string, key: string, kind: string, priority: number }[]` *Normal only*\
  The behaviour attributes a hypothetical allocation may carry, highest priority first, with the bands a numeric attribute is grouped into. Needs the workCenterBehaviorProfile:read permission.

- `ctx.twin.project(input: { applyPaceDrift: boolean | nil, bucketMinutes: number | nil, horizonHours: number | nil, hypotheticalAllocations: { attributes: { [key: string]: string | number } | nil, dependsOn: string[] | nil, dueAt: string | nil, jobLabel: string | nil, label: string, quantity: number | nil, runMinutes: number | nil, setupMinutes: number | nil, startAt: string, workCenterId: string }[] | nil, samples: number | nil, scheduleScenarioId: string | nil, seed: number | nil, workCenterId: string }) -> ok, result: { allocations: { attributeValues: { key: string, value: string }[], endP10At: string, endP50At: string, endP90At: string, evidenceDepth: number, label: string | nil, operationId: string | nil, plannedEndAt: string, plannedStartAt: string, probabilityLaterThanPlanned: number, truncatedAtHorizonProbability: number, workCenterId: string }[], bucketMinutes: number, calendarScheme: string, horizonEndAt: string, horizonStartAt: string, machineStatus: string | nil, samples: number, scheduleScenarioId: string | nil, seed: number, slices: { availableMinutes: number, blockIndex: number | nil, dayKind: string | nil, endAt: string, expectedBusyMinutes: number, expectedLostMinutes: number, expectedStops: number, kind: string | nil, lostMinutesP10: number, lostMinutesP90: number, shiftDefinitionId: string | nil, shiftName: string | nil, startAt: string, stopProbability: number }[], strip: { availableShare: number, downProbability: number, endAt: string, expectedBusyShare: number, startAt: string }[], summary: { boundedAllocations: number, expectedLostMinutes: number, expectedStops: number, expectedUtilization: number, truncatedAllocations: number }, timeZone: string | nil, timeZoneUnset: boolean, uncalibrated: boolean, workCenterId: string }` *Normal only*\
  Projects one work centre over the next N hours (default 24, at most 336) from its behaviour profile and its published plan: per shift or time-bucket occurrence the expected busy and lost minutes with a P10–P90 band on the loss, the probability of a stop and the expected number of stops; a strip of down probability per bucket (5, 15, 30 or 60 minutes); and P10/P50/P90 ends per allocation. Hypothetical allocations must be on this work centre and start inside the window. The same input and result as the `projectWorkCenterTwin` GraphQL query, with `attributes` written as a plain table; timestamps are ISO8601 UTC instants and slice keys carry the tenant-local shift identity. Persists nothing. Needs the scenarioSimulationRun:execute permission.

- `ctx.twin.shadow(workCenterId: string) -> ok, result: { catchUpConfidence: number | nil, evaluatedAt: string, operations: { attributeValues: { key: string, value: string }[], catchUpConfidence: number | nil, elapsedMinutes: number, evidenceDepth: number, expectedEndP10At: string | nil, expectedEndP50At: string | nil, expectedEndP90At: string | nil, jobId: string | nil, observedPaceUnitsPerMinute: number | nil, operationId: string, phase: string, phaseStartedAt: string, plannedEndAt: string | nil, projectedEndAt: string | nil, scenarioId: string | nil, since: string | nil, state: string }[], pace: { driftRatio: number | nil, since: string, state: string } | nil, state: string, workCenterId: string }` *Normal only*\
  The live shadow of a work centre, computed now: what is running on it, the profile's expected finish, the observed pace, each operation's state (ON_TRACK, DRIFTING, STALLED, PAUSED, DOWN, DOWN_OVERDUE, UNCALIBRATED; IDLE with nothing running) and the catch-up confidence over the machine's own queue. A work centre may run several operations at once; the work centre's `state` is the most severe of them. `pace` is the pace track's current state (STEADY or SLOWING with the drift ratio and since when), or nil when the machine has no pace judgement. The same shape as the `workCenterTwinShadow` GraphQL query; timestamps are ISO8601 strings. Needs the workCenterBehaviorProfile:read permission.

- `ctx.twin.simulate(input: { applyPaceDrift: boolean | nil, baseScheduleScenarioId: string | nil, hypotheticalAllocations: { attributes: { [key: string]: string | number } | nil, dependsOn: string[] | nil, dueAt: string | nil, jobLabel: string | nil, label: string, quantity: number | nil, runMinutes: number | nil, setupMinutes: number | nil, startAt: string, workCenterId: string }[], samples: number | nil, seed: number | nil }) -> ok, result: { allocations: { attributeValues: { key: string, value: string }[], endP10At: string, endP50At: string, endP90At: string, evidenceDepth: number, label: string | nil, operationId: string | nil, plannedEndAt: string, plannedStartAt: string, probabilityLaterThanPlanned: number, truncatedAtHorizonProbability: number, workCenterId: string }[], jobs: { completionP50At: string, completionP90At: string, dueAt: string | nil, jobId: string | nil, jobLabel: string | nil, onTimeProbability: number | nil }[], summary: { expectedLateJobs: number, makespanP50At: string, makespanP90At: string, samples: number, truncatedAllocations: number, uncalibratedWorkCenterIds: string[] }, workCenters: { expectedUnplannedDowntimeMinutes: number, expectedUtilization: number, latenessContributionMinutes: number, riskRank: number, workCenterId: string }[] }` *Normal only*\
  Replays hypothetical work against the work-centre twin and returns on-time probabilities per job, P10/P50/P90 finishes per allocation and a risk ranking per work centre. The same input and result as the `simulateWorkload` GraphQL field, with `attributes` written as a plain table; timestamps are ISO8601 strings. Needs the scenarioSimulationRun:execute permission.

## Objects

### Device

A device, reached through its connection. Returned by `ctx.device.get`.

- `device:http(request: { auth: { basic: { password: string, username: string } | nil, bearer: string | nil } | nil, body: string | nil, headers: { [key: string]: string } | nil, method: string | nil, path: string | nil, query: { [key: string]: any } | nil, timeoutMs: number | nil, url: string | nil }) -> ok, result: { body: string | nil, headers: { [key: string]: string }, status: number }`\
  Sends an HTTP request through the device's connection; `path` is relative to its base URL.

- `device:id() -> ok, result: string`\
  The device's id.

- `device:jmf(message: string, path?: string) -> ok, result: { body: any, headers: { [key: string]: string }, status: number }`\
  Posts a JMF message (built with `ctx.jmf.build`) to the device; `path` defaults to `/`.

- `device:sql(request: { bindings: any[] | nil, query: string }) -> ok, result: { columns: string[], executionTimeMs: number | nil, rows: any[][], rowsAffected: number | nil }`\
  Runs a SQL query through the device's database connection; cells come back as plain values, SQL NULL as nil. Edge envelopes, including SQL results, are limited to 16 MiB (16,777,216 bytes, including protocol overhead); select needed columns and page large result sets.

### MLModel

A machine-learning model trained in your account. Returned by `ctx.ml.get`.

- `model:forecast(horizon: number, level?: number) -> ok, result: { level: number | nil, lower: number[] | nil, point: number[], upper: number[] | nil }`\
  Forecasts the next `horizon` periods of a time-series model, with an optional confidence level for the interval.

- `model:info() -> ok, result: { backend: string, concept: string, modelType: string, name: string, status: string }`\
  The model's name, type, backend, concept and status.

- `model:predict(features: { [key: string]: any } | string) -> ok, result: { confidence: number | nil, predictedClass: string | nil, prediction: number | nil, warnings: string[] | nil }`\
  Predicts from one set of features.

### Template

A stored template. Returned by `ctx.template.get`.

- `template:id() -> ok, result: string`\
  The template's id.

- `template:name() -> string`\
  The name the template was looked up by.

- `template:render(vars?: { [key: string]: any }) -> ok, result: string`\
  Renders the template with these variables.

### WorkCenterDevice

The device assigned to a work center. Returned by `ctx.device.forWorkcenter`.

It has the same methods as Device.