What you will build
You will deliver a pinned local connector event through Grid's ingress boundary and prove that an operator-configured binding updates one declared model input and two downstream outputs. You will then send a deliberate second value, diagnose an unbound source ID, and repair it without reusing an event identity.
This build uses local JSON fixtures rather than a live weather provider. It does not claim a published Runtime Host receipt.
Before you begin
You need:
- a running Grid Server Build with Node and Runtime Host ready;
curlandjq;- a public API bearer token for model lifecycle;
- a separate connector-ingress bearer token;
- the three assets above saved as
connector-weather.grid,weather-event.json, andweather-event-warm.json; - an operator-configured delivery binding with this exact contract:
| Binding field | Required value |
|---|---|
| HTTP source ID | weather |
| Delivered source symbol | temperature |
| Target model | connector-weather |
| Writable target symbol | Temperature |
| Value contract | finite JSON number |
The public API does not currently document a route for creating this delivery binding. Have the operator configure and verify it for the exact deployment before continuing. Connector ingress is not an arbitrary cell-write endpoint.
export GRID_URL="http://localhost:3000"
export GRID_TOKEN="replace-with-your-api-token"
export GRID_INGRESS_TOKEN="replace-with-your-ingress-token"
These are distinct authorities. Never put either token in a fixture, source file, URL, or commit. Requests go to Node; Runtime Host and its private RPC token stay inside the deployment network. Review connector authentication and API bearer boundaries.
1. Validate the local event fixtures
Check required fields and identities before any network call:
jq -e '
.sourceKind == "stream" and
(.events | length) == 1 and
.events[0].sourceSymbol == "temperature" and
(.events[0].value | numbers) and
(.events[0].sequence | numbers) and
(.events[0].idempotencyKey | strings)
' weather-event.json weather-event-warm.json
jq -s '[.[] | {
value: .events[0].value,
sequence: .events[0].sequence,
idempotencyKey: .events[0].idempotencyKey
}]' weather-event.json weather-event-warm.json
Deterministic local checkpoint:
[
{"value":21.5,"sequence":7,"idempotencyKey":"weather-temperature-7"},
{"value":24,"sequence":8,"idempotencyKey":"weather-temperature-8"}
]
This proves valid JSON and stable fixture identities only. It does not prove a binding exists or an event was admitted.
2. Deploy the target model
Build a protected-mode request from the exact source:
jq -n --rawfile source connector-weather.grid '{
id: "connector-weather",
protectedMode: true,
source: $source
}' > /tmp/connector-weather-deploy.json
curl --fail-with-body \
-H "Authorization: Bearer $GRID_TOKEN" \
-H "Content-Type: application/json" \
-X POST "$GRID_URL/api/models" \
--data-binary @/tmp/connector-weather-deploy.json \
| jq '{id, sourceHash, declaredInputs, declaredOutputs, diagnostics}'
Expected checkpoint on a compatible build: Temperature is the only declared input; AdjustedTemperature and NeedsCooling are declared outputs.
Resolve the baseline:
curl --fail-with-body \
-H "Authorization: Bearer $GRID_TOKEN" \
-H "Content-Type: application/json" \
-X POST "$GRID_URL/api/models/connector-weather/symbols/resolve" \
--data '{"symbols":["Temperature","AdjustedTemperature","NeedsCooling"]}' \
| jq '{
temperature: .Temperature.value.value,
adjusted: .AdjustedTemperature.value.value,
needsCooling: .NeedsCooling.value.value
}'
Expected live checkpoint: 20, 20.5, and false.
3. Send the first connector event
The URL source ID must exactly match weather after URL decoding:
curl --fail-with-body \
-H "Authorization: Bearer $GRID_INGRESS_TOKEN" \
-H "Content-Type: application/json" \
-X POST "$GRID_URL/api/pipes/sources/weather/events" \
--data-binary @weather-event.json \
| tee /tmp/weather-event-response.json \
| jq '{sourceId, admitted, deduped, coalesced, unbound, applied, stream}'
Do not treat admitted as proof of a model update. For this resident, correctly bound target, confirm unbound = 0 and applied reports at least one applied write. Then resolve the three symbols again.
Expected downstream checkpoint after a confirmed application:
Temperature = 21.5,AdjustedTemperature = 22, andNeedsCooling = false.
If stream.batchStatus is unsubscribed, no active exact-source Stream registration accepted the event into a durable Stream log. That status does not by itself contradict a delivery binding on the existing realtime lane; use unbound and applied to diagnose this tutorial's target write.
4. Make one deliberate value change
Send the second fixture with a new sequence and idempotency key:
curl --fail-with-body \
-H "Authorization: Bearer $GRID_INGRESS_TOKEN" \
-H "Content-Type: application/json" \
-X POST "$GRID_URL/api/pipes/sources/weather/events" \
--data-binary @weather-event-warm.json \
| tee /tmp/weather-warm-response.json \
| jq '{sourceId, admitted, deduped, unbound, applied, stream}'
After unbound = 0 and a confirmed applied write, resolve the model again.
Expected downstream checkpoint:
Temperature = 24,AdjustedTemperature = 24.5, andNeedsCooling = true.
The model owns the calibration and threshold. The connector owns observation identity and delivery; it does not reproduce either formula.
5. Confirm idempotent retry behavior
Send weather-event-warm.json again without changing one byte. A safe retry preserves the same source ID, sequence, idempotency key, and content.
curl --fail-with-body \
-H "Authorization: Bearer $GRID_INGRESS_TOKEN" \
-H "Content-Type: application/json" \
-X POST "$GRID_URL/api/pipes/sources/weather/events" \
--data-binary @weather-event-warm.json \
| jq '{admitted, deduped, applied, stream}'
Expected checkpoint: the response reports a deduplicated or duplicate delivery rather than applying a second logical observation. Preserve the complete response because durable Stream and realtime delivery counters have distinct meanings.
Do not reuse weather-temperature-8 with different content. Conflicting reuse can return 409; a genuinely new observation needs a new sequence and idempotency key.
6. Cause an unbound delivery, then repair it
Create a disposable event identity, then send it to a misspelled source ID. The JSON remains valid, but the URL binding is wrong:
jq '
.events[0].sequence = 9 |
.events[0].idempotencyKey = "weather-temperature-9-typo"
' weather-event.json > /tmp/weather-event-unbound.json
curl --fail-with-body \
-H "Authorization: Bearer $GRID_INGRESS_TOKEN" \
-H "Content-Type: application/json" \
-X POST "$GRID_URL/api/pipes/sources/weather-typo/events" \
--data-binary @/tmp/weather-event-unbound.json \
| tee /tmp/weather-unbound-response.json \
| jq '{sourceId, admitted, deduped, unbound, applied, stream}'
Expected failure signal: no write is applied; the response reports an unbound or unsubscribed source path for this deployment. Temperature remains 24.
Minimal repair:
- Compare the response
sourceIdwith the binding contract. - Restore the exact
/sources/weather/eventspath. - For a new logical observation, send a new sequence and idempotency key. For a retry of an identical prior observation, reuse its complete original identity and content.
Do not “repair” this by routing around ingress with a direct arbitrary cell write. See connector troubleshooting.
7. Clean up
curl --fail-with-body \
-H "Authorization: Bearer $GRID_TOKEN" \
-X DELETE "$GRID_URL/api/models/connector-weather"
Expected response: true. Ask the operator to remove the tutorial binding if it was created only for this exercise.
You are done when
- Both local fixtures pass the deterministic identity check.
- Public API and ingress credentials remain separate and uncommitted.
- The baseline event has
unbound = 0, a confirmed applied write, and resolves to22downstream. - The
24event changes only the input and lets the model derive24.5andtrue. - An identical retry is deduplicated, while the misspelled source path applies no write.
- You can explain why admission, durable Stream status, binding, and application are different counters.
Use Send connector events to Grid for the complete event and dense-frame contracts, and Troubleshoot Grid for overload and Runtime Host failures.