Curriculum / Frontend & OSDK Developer
Apply Ontology Actions and Execute Queries from the app
Introduction
Scenario: Your mission command app already reads the supply-chain Ontology; now triage officers need to act on it — assign an owner to a disruption, escalate it, and pull a recommended mitigation — without leaving the app. You will wire an Ontology Action behind a UI control and call a published query, then prove the effect the only way Foundry's read APIs allow.
Until now the mission command app has been read-only: it lists Shipments, searches the Ontology, and aggregates KPIs over an OAuth2-authenticated client scoped to api:ontologies-read. In this unit you make the app write back. A triage officer clicks 'Assign owner' or 'Escalate' and the app calls Apply Action — the Ontology REST API v2 endpoint that runs a governed Action type against real object and link state. Apply Action is a WRITE: it requires both api:ontologies-read and api:ontologies-write OAuth2 scopes, which is the first place least-privilege thinking bites. A read view needs only the read scope; the moment a control mutates the Ontology you must request write, and you should request it for nothing more than the Action types this app actually fires.
Alongside the write path you will add an analytic path: a panel that calls a published query (a deployed Function / AIP Logic) via Execute Query. Execute Query runs the latest published version of a named query and returns a structured value; it needs only api:ontologies-read because, from the platform's perspective, running the published function and returning its output is a read-shaped operation. The honest limit you must internalize this unit is what is and is not confirmable from outside the app. The button, its click handler, the OSDK call, the OAuth2 client config, and the React panel are all client-side code — no documented Foundry READ API introspects your repository, your generated OSDK package, or your components. What the platform CAN confirm is the Ontology your app consumes and the consequences of what it did: that the Action type your button targets exists, that an object's state changed the way an applied Action would change it, and that Execute Query returns the expected output shape.
That gap drives the whole unit. Because Apply Action is a write with no read-side introspection of the apply itself, you do not 'read the action that ran' — you read the resulting object state and reason backward: a DisruptionEvent that now has an owner and a triaged status is the read-confirmable proof your write landed. Execute Query is similarly check-from-outside: the platform runs the function and asserts the returned value contains the keys you expect (recommendation, rationale, confidence); it does not, and should not, introspect the function's internals. Your deliverable is an interactive Action control plus a query panel, written by you as a coding-capable developer, with a clear-eyed account of which parts are attested client code and which parts the Ontology proves.
Capability focus: Apply Action (write, read+write scopes); Execute Query from the app; proving the write via object state. · Artifact: An interactive control that fires an Ontology Action plus a panel that calls a published query.
Key concepts
- Apply Action (write): POST /api/v2/ontologies/{ontology}/actions/{actionType}/apply runs a governed Ontology Action that modifies object and link state. It is a WRITE operation and requires BOTH the api:ontologies-read and api:ontologies-write OAuth2 scopes; an applied Action is therefore confirmed on the read side by its resulting object state, not by reading the apply call itself.
- Action types vs. applied Actions: An Action TYPE is the parameterized, governed definition (e.g. assignDisruptionOwner, escalateDisruption) that List Action Types (GET .../actionTypes, api:ontologies-read) can confirm exists. Confirming the TYPE exists proves the app targets something real; it is not proof that any instance of the Action was applied — only changed object state shows that.
- Execute Query: POST /api/v2/ontologies/{ontology}/queries/{queryApiName}/execute runs a published/deployed query (the latest published version by default) and returns a structured value field. It requires only the api:ontologies-read OAuth2 scope. The platform runs the function and asserts the output shape; it does not introspect the function's internal logic.
- Read-confirmable vs. attested surface: The app is client-side code. Its repository contents, generated OSDK package, React components, OAuth2 client configuration, and UI rendering are NOT exposed by any documented Foundry READ API — they are manual/attested. The read-confirmable surface is the Ontology the app consumes (object and Action types, object state) plus the published query's Execute Query output.
- Least-privilege scope escalation: Adding a write control means requesting api:ontologies-write in addition to api:ontologies-read. Over-broad scoping (granting write across more Action types or entities than the app fires, or carrying write on read-only flows) violates the OSDK's least-privilege model, in which the token is scoped only to the entities the application is allowed to access on top of the user's own permissions.
- Proving a write via object state: Because no read API introspects the apply, you confirm the effect by reading the objects the Action touched — for example, List Objects over DisruptionEvent showing at least one event with a non-empty owner and a status in {assigned, resolved}. This object-state pattern is the canonical way to validate an OSDK write from outside the app.
Companion video
Using Actions in your OSDK App · open on YouTube
Hands-on activity
each step validates · the unit completes when all steps pass- 1
Wire an Apply Action call behind a UI control
Wire a UI control — an 'Assign owner' or 'Escalate' button — to Apply Action through the OSDK or a direct call to POST /api/v2/ontologies/{ontology}/actions/{actionType}/apply, passing the Action's parameters (for example the disruption's id and the chosen owner). This is the write path, so the client must hold an OAuth2 token carrying BOTH api:ontologies-read and api:ontologies-write; request write only for the Action types this app actually fires. Be clear-eyed about what this step delivers: the button, its click handler, the OSDK invocation, and the client config are all client-side code that no documented Foundry READ API can introspect, so this step is self-attested. You are confirming that you wired the call and requested the correct scopes; the effect of running it is proven in a later step, because Apply Action is a write with no read-side view of the apply itself.
not startedself-attestedSelf-attested: a button is wired to Apply Action (a WRITE requiring read+write scopes).
- 2
Confirm the action type the app fires exists
Confirm that the Action types your control targets actually exist in the Ontology by listing Action types (GET .../actionTypes, which needs only api:ontologies-read) and verifying that assignDisruptionOwner and escalateDisruption are present with the parameters your handler passes. This is an existence check against the read-confirmable surface: it proves the app is firing at a real, governed Action type rather than a name you invented, and it catches a mistyped or unpublished apiName before you debug the UI. Keep the distinction sharp — confirming the TYPE exists is not proof that any instance was applied. It only establishes that the target of your write is real; whether a write actually landed is a separate, object-state question handled next.
not startedinstance checkConfirms the app targets real action types (assignDisruptionOwner, escalateDisruption).
- 3
Prove the action actually landed via object-state
Prove the applied Action actually landed by reading object state, since this is the only read-confirmable evidence Foundry exposes for a write — there is no read introspection of the apply call itself. List Objects over DisruptionEvent and verify that at least one event now has a non-empty owner and a status in {assigned, resolved}, which is exactly the change an assign-owner or escalate Action produces. Reason backward from the data: the object's new state is the fingerprint your write left behind. This mirrors the curriculum's write-confirmation pattern (R-VAL2) — you do not confirm that the Action ran, you confirm that the Ontology changed the way an applied Action would change it, and you treat that changed object state as the proof your interactive control did its job.
not startedinstance checkConfirms an applied action: a DisruptionEvent now has an owner and a triaged status.
- 4
Call a published query/function and check the structured result
Add the analytic panel by calling Execute Query (POST /api/v2/ontologies/{ontology}/queries/{queryApiName}/execute, requiring only api:ontologies-read) against the published recommendMitigation function for a seeded disruption (disruptionId 'DISR-1'), and confirm the returned value contains the expected keys: recommendation, rationale, and confidence. This is execute-only validation (R-VAL1): the platform runs the latest published version of the function and asserts the output SHAPE, but it does not — and should not — introspect the function's internal logic. Note the scope contrast that defines this unit: the mitigation query is a read-scoped Execute Query returning a structured result, while the Action control next to it is a read+write Apply Action proven only by changed object state. Wiring the panel and rendering the result is attested client code; the structured Execute Query output is the read-confirmable part.
not startedinstance checkExecutes the published recommendMitigation function and confirms the expected structured keys.

