Drive Mesh Desk from your own code
The page reads your Istio configuration in the browser for free — VirtualService,
DestinationRule, Gateway, ServiceEntry, PeerAuthentication, AuthorizationPolicy and
RequestAuthentication, plus the Kubernetes Services and Deployments behind a host — parses
every document with its line numbers, and runs deterministic checks over it: routes to subsets no
DestinationRule defines, weights that do not total 100, catch-all routes that shadow the ones
below, retries that cannot fit their timeout, fault injection and mirroring left on, and, for
mutual TLS, the PeerAuthentication posture per scope, DestinationRules that switch TLS off and
AuthorizationPolicies plaintext walks past. Its two paid steps are available over HTTP. You send
one JSON object with task set to "traffic" or "mtls", the
pasted YAML as config and the page’s check as a facts string, and
the model replies with one JSON object: a routing audit with a YAML fix per finding and an answer
for every flag, or an mTLS posture per scope with the fixes and a rollout to STRICT.
Base URL: https://api.skillsafe.ai/v1/app-api. Every call sends
Content-Type: application/json and, once you have a token,
Authorization: Bearer <token>. The paths below (/guest,
/me, /estimate, /run, /jobs/{job_id},
/run-stream) are relative to it, exactly as sdk.js calls them. A
plain-text summary of the app for agents is at /llms.txt.
The response envelope
Responses are JSON of the form {"ok": ..., "data": ..., "error": {...}}. The page’s
SDK (sdk.js) decides success from the HTTP status, not from ok: on a
2xx it returns data; otherwise it raises an error whose message is
error.message (or the HTTP status text when the body carries none), with
status, code (error.code) and details
(error.details) attached. A body that is not JSON is treated as {}.
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "...", "details": { ... } } }
Error codes
Branch on the HTTP status and error.code, show error.message. The page
handles 401 and 402 by name; the rest reach it as a generic failure (“The run failed: ...”).
| HTTP | error.code | What it means / what to do |
|---|---|---|
400 | VALIDATION_ERROR | The body is not a JSON object or a declared field has the wrong type. Every field is a string; sending facts or traffic_handoff as an object is the usual cause. |
401 | UNAUTHORIZED | No token, or one that expired or was revoked. The page forgets the signed-in user and says “Your session expired - sign in again.” Get a new token. |
402 | INSUFFICIENT_CREDITS | The balance cannot cover the run. The page says “Not enough credits for this run.” with a top-up link, and tries never to submit into a 402: it disables the run button when the /me balance is below the estimate’s min_credits. |
404 | NOT_FOUND | A wrong path, or a job id that does not belong to this token. |
409 | CONFLICT | An Idempotency-Key replayed with a different body. Send the original body, or use a new key (the page bumps its :a<attempt> suffix for a retry). |
429 | RATE_LIMITED | Too many requests. Back off with a delay; do not tight-loop, and never resend a metered run without its original Idempotency-Key. |
Failures that do not arrive as an HTTP status:
| What | What happens |
|---|---|
event: error on /run-stream | The SDK raises an error carrying the event’s message, code and job_id. |
Job status: "failed" | Polling stops; the job’s error field says why. |
| Polling past 180 s | The SDK’s own client-side limit (waitForJob, 1 s interval, 180 000 ms default): it throws “job timed out”. The job itself is not cancelled. |
The task field: two lanes
task routes the run, and it takes exactly two values. Each lane has its own reply
contract, and a reply never mixes them. The skill’s instructions tell the model to do the
traffic lane when task is missing or anything else, so a typo silently gets you a
traffic audit: always send one of the two values, and send the facts built for that
same lane.
task | What it does | verdict | Use it when |
|---|---|---|---|
"traffic" | Audits how requests are routed: confirms or dismisses each page flag (TR-01..TR-22), adds what text rules cannot see, gives the verbatim evidence and a YAML fix for each finding, says in plain words what each VirtualService host’s requests get today, and orders the fixes into a plan. | unsafe | needs_work | ready | Before applying routing changes: canary splits, retries and timeouts, circuit breakers, mirrors, fault injection, gateways. |
"mtls" | Checks the mutual-TLS posture of the same config: answers every posture row the page computed, confirms or dismisses each page flag (MT-01..MT-13), fixes each finding and writes an ordered rollout to STRICT that does not break callers. | exposed | permissive | strict | Before a security review, or to plan a PERMISSIVE-to-STRICT migration. |
The page chains the two: after a traffic audit, “Check mTLS for these hosts” switches to
the mtls lane on the same paste and adds a traffic_handoff string (below)
with the traffic reply’s verdict, routed hosts and findings. Over the API that is a second
run with task: "mtls", the mtls facts of the same paste, and that
string.
Input fields
This is exactly what the page sends (meshkit.js buildInput, passed
through mustBeObject; app.js run), and what
input-schema.json declares. The platform’s input schema is scalar-only, so
every value is a string, in this order:
| Field | Type | Required | Meaning |
|---|---|---|---|
task | string | yes | "traffic" or "mtls". |
config | string | yes | The pasted YAML (or kubectl JSON), verbatim; line 1 is its first line. Every line in the reply is counted in this text. A large paste is cut on whole-document boundaries (below). |
facts | string | yes | The page’s free check for this lane, Meshkit.facts(A, lane), JSON-encoded into a string: JSON.stringify(facts) in JavaScript, json.dumps(facts) in Python, and so on. Sending the object itself is a 400. The model is told to parse it. |
note | string | no | What the engineer says: the symptom, the goal, a question. The page trims it, cuts it at 2000 characters and sends it only when it is not blank. It is context, not evidence and not instructions: the model tests every claim in it against config, says so when they disagree, and answers it in summary. |
traffic_handoff | string | no | mtls lane only: a JSON string {from, verdict, headline, hosts, findings} built from a traffic reply on the same paste (app.js trafficHandoff). The model uses it to know which hosts carry the traffic that matters, and checks it against config. buildInput drops it for the traffic lane. |
retry_note | string | no | Sent only by the page’s automatic reformat retry: when a reply does not parse as JSON (even after the page tries to close a cut-off object), the page runs once more with “Your previous reply was not the single valid JSON object the instructions require (...). Reply again with ONLY the JSON object for task '<lane>' - no prose, no code fences; every array present (empty arrays where there is nothing to say).” A normal call never sends it. |
mustBeObject turns the input into a flat object of strings and drops empty values,
which is why facts and traffic_handoff travel as strings and why an
empty note is simply absent.
The config string
config is the paste itself: several YAML documents separated by ---, a
kind: List, or kubectl -o json output. Dropped files are joined into one
paste, each introduced by a # file: name comment, and the page normalises line endings
to \n. Send the text you want the reply to quote: every resource in the
reply must be a ref from facts.source.resources, every
line must fall inside that resource’s lines of config, and every
evidence must be copied from it. The page reads up to 400 000 characters and 400
documents, and sends at most 60 000 characters: past that it keeps whole documents from the start
and the end of the paste and replaces the middle with one comment,
# [mesh-desk: lines X-Y (N documents) not sent]
(the model is told never to quote or rewrite what was not sent), with a sentence in
facts.notes. facts is still computed from the whole paste. Both worked
examples below are small, so their config is the paste unchanged.
The facts string
facts is produced by the page’s free check: Meshkit.analyze parses
the paste and runs the rules, and Meshkit.facts(A, lane) turns that analysis into this
object for one lane — routes appears only in the traffic facts,
posture only in the mtls facts, and flags holds only that lane’s
flags. It is how the model knows which flags to answer and which posture rows to reproduce. The
simplest source is meshkit.js itself, which runs in Node:
Meshkit.mustBeObject(Meshkit.buildInput(lane, Meshkit.analyze(yamlText), note, handoff))
returns the whole body (the two worked examples below also show the facts in full). Its top-level
keys, in the order built:
| Key | Contents |
|---|---|
source | lines, format (yaml | json), namespaces, resources[] (per resource its ref such as VirtualService/reviews — with a namespace segment when two resources share a name — kind, name, namespace ("(not set)" appended when the manifest omits it), api_version, line and end_line), and up to ten parse_errors. |
hosts | Per fully-qualified host: host, the virtual_services and destination_rules that name it, subsets_defined, subsets_routed, gateways, mtls_server (the PeerAuthentication mode computed for the workloads behind it; "(default)" means Istio’s PERMISSIVE default), mtls_client, and service_entry / service when pasted. |
routes | Traffic lane. Every http route: vs, index, name, line, match, destinations[] (host, subset, weight), timeout, retries, fault, mirror. |
posture | mTLS lane. One row per PeerAuthentication (scope mesh, namespace/<ns> or workload/<ns>/<labels>) and one effective/<ns> row per namespace in the paste, each with mode, source and port-level ports[] (port, mode). |
flags | Every flag the page raised for this lane: uid (F1, F2, ...), id (the catalogue id: TR-01..TR-22 or MT-01..MT-13), sev (high | medium | low | info), resource, line, text and detail. The reply must answer every non-info flag by its uid. |
verdict_candidate | The verdict this lane’s flags point at (the page’s own reading). |
notes | Plain sentences about what was not read or not sent. |
Without the page’s check. facts is required, but the platform
only checks that it is a string. With a hand-made minimal facts the model still reads
the config, but you lose what the check adds: with no flags there is nothing to confirm
or dismiss, with no source.resources there are no refs to name resources by, and with
no posture the mtls reply has no rows to reproduce. Build it with
meshkit.js as above.
The traffic_handoff string
Only for task: "mtls", and only for the paste the traffic reply was about (the page
drops it as soon as the paste is edited). The page builds it from the normalised traffic
reply (recon.js normalize, below):
JSON.stringify({ from: "traffic", verdict: r.verdict, headline: r.headline,
hosts: r.routes.map(x => x.host),
findings: r.findings.map(f => ({ id: f.id, resource: f.resource, severity: f.severity, title: f.title })) })
The mtls reply is then expected to cover every handed-off host with a posture row or a finding, and
to say in summary how the traffic findings interact with mTLS. It is a previous reply,
not evidence: the model checks it against config.
The output contracts
The model is instructed (SKILL.md) to reply with one JSON object and
nothing else — no prose, no code fences — with every array of its lane
present ([] when empty). The run’s output text is that object. Both lanes share
the finding shape
{id, flags[], resource, line, severity, title, evidence, why, fix}: id is
a catalogue id of the lane or other; flags lists the page
uids it confirms ([] for something the page did not flag);
resource is a ref from facts.source.resources, copied
exactly; line is a line of config inside that resource;
severity is high | medium | low;
evidence is one to three lines copied verbatim from config;
fix is a YAML fragment, or a whole corrected resource (with kind,
metadata.name and metadata.namespace) when the fix belongs in another or
a missing resource. And both share
flag_answers[]: {flag, status, reason}, exactly one for every page flag
whose sev is not info, with status confirmed (a
finding lists that uid) | dismissed (with the reason it does not apply).
task: "traffic"
task:"traffic".verdict:unsafe(any finding is high: requests fail or go to the wrong place, or a fault or mirror hits real users) |needs_work(the worst is medium or low) |ready(nothing above low;findingsmay be empty).headline: one sentence.findings[]: as above, withTR-ids.flag_answers[]: as above.routes[]:{host, behaviour}, one per host named in a VirtualServicehostslist, as written there;behavioursays in plain words what a request to that host gets today, route by route, including the effect of any finding.plan[]: the fixes in the order to apply them (fixes that stop failures first; create a subset before routing to it).questions[]: at most 5, only what the config cannot answer.summary: two to four sentences, answeringnoteif one was sent.
task: "mtls"
task:"mtls".verdict:exposed(plaintext reaches a workload that should be protected — any high finding) |permissive(an effective scope is PERMISSIVE, including the Istio default, or the worst finding is medium) |strict(every effective scope is STRICT and nothing is above low).headline: one sentence.posture[]:{scope, mode, source, comment}, one row for every row offacts.posturewith the samescope;modeisSTRICT|PERMISSIVE|DISABLE|UNSETand must agree with the page’s row unlesscommentsays why the page misread it; no scope the paste does not contain.findings[]: as above, withMT-ids.flag_answers[]: as above.rollout[]: concrete, ordered steps to reach STRICT everywhere that matters without an outage, each naming the resource it changes; forstrict, what to monitor to keep it strict.questions[]andsummary, as in the traffic lane.
How the page reads a reply (recon.js parseResult and
normalize): it strips a code fence and parses from the first { to the
last }. A reply is read as the lane that was asked for, unless it names the other lane
and carries that lane’s body (routes for traffic, posture
for mtls). An unknown verdict shows as needs_work or
permissive and counts as a disagreement; an unknown severity becomes
medium and counts as one too; ids like TR3 or MT_4 become
TR-03 and MT-04; flags keeps only F<n>
values; findings with no resource, evidence and title are dropped; questions is capped
at 5, plan and rollout at 12. A reply carries 8 sections in either lane
(verdict, headline and six more), which is what the page counts when it recovers a partial reply.
Worked examples
Both are built with the app’s own modules from the examples the page ships (the example
buttons on the app page), exactly as the page builds them:
Meshkit.mustBeObject(Meshkit.buildInput(e.lane, Meshkit.analyze(e.raw), e.note)). Each
shows the config text and the decoded facts object (save them under the
file names given to run the samples in steps 3–5), the request body, and a saved model reply
(reply set r2).
Traffic: A reviews canary that shadows its testers route, plus a ratings fault left on
4 resources: VirtualService/reviews, DestinationRule/reviews, VirtualService/ratings, DestinationRule/ratings. The page’s free check reads 3 http routes and raises 8 flags (3 high, 3 medium, 2 low) for the traffic lane; its own reading of the verdict is unsafe. The engineer’s note: “We shipped reviews v3 as a 5% canary an hour ago. The testers (end-user: jason) say they never see v2, and ratings errors jumped. What is wrong before I apply the next change?”
The config string, 1,587 characters ending in a newline — save it as traffic-config.yaml:
# file: bookinfo/reviews-routing.yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews
namespace: bookinfo
spec:
hosts:
- reviews
http:
- name: canary-split
route:
- destination:
host: reviews
subset: v1
weight: 90
- destination:
host: reviews
subset: v3
weight: 5
- name: testers
match:
- headers:
end-user:
exact: jason
route:
- destination:
host: reviews
subset: v2
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: reviews
namespace: bookinfo
spec:
host: reviews
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
---
# file: bookinfo/ratings-routing.yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: ratings
namespace: bookinfo
spec:
hosts:
- ratings
http:
- fault:
abort:
percentage:
value: 20
httpStatus: 503
route:
- destination:
host: ratings
subset: v1
timeout: 2s
retries:
attempts: 3
perTryTimeout: 1s
retryOn: 5xx,connect-failure
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: ratings
namespace: bookinfo
spec:
host: ratings
trafficPolicy:
connectionPool:
http:
http1MaxPendingRequests: 100
subsets:
- name: v1
labels:
version: v1
The facts object, decoded and complete — save it as traffic-facts.json:
{
"source": {
"lines": 86,
"format": "yaml",
"namespaces": [
"bookinfo"
],
"resources": [
{
"ref": "VirtualService/reviews",
"kind": "VirtualService",
"name": "reviews",
"namespace": "bookinfo",
"api_version": "networking.istio.io/v1",
"line": 2,
"end_line": 29
},
{
"ref": "DestinationRule/reviews",
"kind": "DestinationRule",
"name": "reviews",
"namespace": "bookinfo",
"api_version": "networking.istio.io/v1",
"line": 31,
"end_line": 44
},
{
"ref": "VirtualService/ratings",
"kind": "VirtualService",
"name": "ratings",
"namespace": "bookinfo",
"api_version": "networking.istio.io/v1",
"line": 47,
"end_line": 69
},
{
"ref": "DestinationRule/ratings",
"kind": "DestinationRule",
"name": "ratings",
"namespace": "bookinfo",
"api_version": "networking.istio.io/v1",
"line": 71,
"end_line": 85
}
],
"parse_errors": []
},
"hosts": [
{
"host": "ratings.bookinfo.svc.cluster.local",
"virtual_services": [
"VirtualService/ratings"
],
"destination_rules": [
"DestinationRule/ratings"
],
"subsets_defined": [
"v1"
],
"subsets_routed": [
"v1"
],
"gateways": [],
"mtls_server": "PERMISSIVE (default)",
"mtls_client": "ISTIO_MUTUAL (auto)"
},
{
"host": "reviews.bookinfo.svc.cluster.local",
"virtual_services": [
"VirtualService/reviews"
],
"destination_rules": [
"DestinationRule/reviews"
],
"subsets_defined": [
"v1",
"v2"
],
"subsets_routed": [
"v1",
"v3",
"v2"
],
"gateways": [],
"mtls_server": "PERMISSIVE (default)",
"mtls_client": "ISTIO_MUTUAL (auto)"
}
],
"routes": [
{
"vs": "VirtualService/reviews",
"index": 0,
"name": "canary-split",
"line": 11,
"match": "every request",
"destinations": [
{
"host": "reviews",
"subset": "v1",
"weight": 90
},
{
"host": "reviews",
"subset": "v3",
"weight": 5
}
],
"timeout": null,
"retries": null,
"fault": false,
"mirror": false
},
{
"vs": "VirtualService/reviews",
"index": 1,
"name": "testers",
"line": 21,
"match": "header end-user exact jason",
"destinations": [
{
"host": "reviews",
"subset": "v2",
"weight": null
}
],
"timeout": null,
"retries": null,
"fault": false,
"mirror": false
},
{
"vs": "VirtualService/ratings",
"index": 0,
"name": "",
"line": 56,
"match": "every request",
"destinations": [
{
"host": "ratings",
"subset": "v1",
"weight": null
}
],
"timeout": "2s",
"retries": {
"attempts": 3,
"perTryTimeout": "1s",
"retryOn": "5xx,connect-failure"
},
"fault": true,
"mirror": false
}
],
"flags": [
{
"uid": "F1",
"id": "TR-03",
"sev": "high",
"resource": "VirtualService/reviews",
"line": 11,
"text": "http[0] (canary-split) matches every request, so the route below it never runs",
"detail": "Istio evaluates http routes top to bottom and stops at the first match; put the route with no match last."
},
{
"uid": "F2",
"id": "TR-01",
"sev": "high",
"resource": "VirtualService/reviews",
"line": 11,
"text": "The route to reviews subset v3 names a subset DestinationRule/reviews does not define (v1, v2)",
"detail": "Envoy has no cluster for an undefined subset: those requests fail with 503 (NR / no healthy upstream)."
},
{
"uid": "F3",
"id": "TR-10",
"sev": "high",
"resource": "VirtualService/ratings",
"line": 56,
"text": "http[0] injects aborts into every request it routes",
"detail": "Fault injection is a chaos-test tool; left in a production VirtualService it fails real requests."
},
{
"uid": "F4",
"id": "TR-02",
"sev": "medium",
"resource": "VirtualService/reviews",
"line": 12,
"text": "http[0] splits traffic 90/5 = 95, not 100",
"detail": "Istio's validation expects the weights of one route to total 100. Older control planes reject the resource; newer ones scale the numbers, so the split is not the one written."
},
{
"uid": "F5",
"id": "TR-04",
"sev": "medium",
"resource": "VirtualService/ratings",
"line": 66,
"text": "http[0] allows 3 retries of 1s but the 2s timeout leaves room for 1",
"detail": "(1 + attempts) x perTryTimeout = 4s exceeds the route timeout; the last retries are cut off."
},
{
"uid": "F6",
"id": "TR-07",
"sev": "medium",
"resource": "DestinationRule/ratings",
"line": 76,
"text": "DestinationRule/ratings sets no outlierDetection for ratings, while routes to it retry",
"detail": "Without outlier detection a failing pod keeps its share of traffic; retries then land on it again."
},
{
"uid": "F7",
"id": "TR-06",
"sev": "low",
"resource": "VirtualService/reviews",
"line": 10,
"text": "2 of 2 http routes in VirtualService/reviews set no timeout",
"detail": "Istio sets no HTTP route timeout by default, so a hung backend holds the caller's connection until the caller gives up."
},
{
"uid": "F8",
"id": "TR-07",
"sev": "low",
"resource": "DestinationRule/reviews",
"line": 36,
"text": "DestinationRule/reviews sets no outlierDetection for reviews",
"detail": "Without outlier detection a failing pod keeps its share of traffic; retries then land on it again."
}
],
"verdict_candidate": "unsafe",
"notes": []
}
The request body the page sends, trimmed for display: the full body is
6,851 characters of compact JSON, and the two long strings are cut here after their first
characters, with the cut marked inside the string. Untrimmed, config is the first
block exactly and facts is the compact JSON.stringify of the second; the
JavaScript makeInput in step 3 rebuilds the body byte for byte from the two saved
files (check.js asserts it).
{
"task": "traffic",
"config": "# file: bookinfo/reviews-routing.yaml\napiVersion: networking.istio.io/v1\nkind: VirtualService\nmetadata:\n name: reviews\n namespace: bookinfo\nspec:\n hosts:\n ... [trimmed for display: 160 of 1,587 characters shown]",
"facts": "{\"source\":{\"lines\":86,\"format\":\"yaml\",\"namespaces\":[\"bookinfo\"],\"resources\":[{\"ref\":\"VirtualService/reviews\",\"kind\":\"VirtualService\",\"name\":\"reviews\",\"namespace ... [trimmed for display: 160 of 4,425 characters shown]",
"note": "We shipped reviews v3 as a 5% canary an hour ago. The testers (end-user: jason) say they never see v2, and ratings errors jumped. What is wrong before I apply the next change?"
}
The page would send this with Idempotency-Key: mesh-desk:traffic:d50ba79b-1ac3:a1 (see step 4). The saved
reply, in full:
{
"task": "traffic",
"verdict": "unsafe",
"headline": "The reviews route's catch-all shadows the testers rule and sends 5% of traffic to an undefined v3 subset, while ratings runs a 20% fault-abort on every request and a retry budget that outruns its timeout.",
"findings": [
{
"id": "TR-03",
"flags": [
"F1"
],
"resource": "VirtualService/reviews",
"line": 11,
"severity": "high",
"title": "canary-split has no match and sits above testers, so testers never runs",
"evidence": "- name: canary-split\n route:\n - name: testers",
"why": "http[0] (canary-split) has no match condition, so Istio's top-to-bottom evaluation matches every request there, including jason's; http[1] (testers) with the end-user: jason header match is never reached. That is exactly why the testers say they never see v2.",
"fix": "apiVersion: networking.istio.io/v1\nkind: VirtualService\nmetadata:\n name: reviews\n namespace: bookinfo\nspec:\n hosts:\n - reviews\n http:\n - name: testers\n match:\n - headers:\n end-user:\n exact: jason\n route:\n - destination:\n host: reviews\n subset: v2\n - name: canary-split\n route:\n - destination:\n host: reviews\n subset: v1\n weight: 90\n - destination:\n host: reviews\n subset: v3\n weight: 5\n"
},
{
"id": "TR-01",
"flags": [
"F2"
],
"resource": "VirtualService/reviews",
"line": 19,
"severity": "high",
"title": "canary-split routes 5% of reviews traffic to subset v3, which is not defined",
"evidence": " host: reviews\n subset: v3\n weight: 5",
"why": "DestinationRule/reviews only defines subsets v1 and v2, so Envoy has no cluster for v3; the 5% of requests sent there fail with 503 (no healthy upstream). This is the v3 canary's own 5% failing outright, separate from the testers being shadowed.",
"fix": "apiVersion: networking.istio.io/v1\nkind: DestinationRule\nmetadata:\n name: reviews\n namespace: bookinfo\nspec:\n host: reviews\n subsets:\n - name: v1\n labels:\n version: v1\n - name: v2\n labels:\n version: v2\n - name: v3\n labels:\n version: v3\n"
},
{
"id": "TR-10",
"flags": [
"F3"
],
"resource": "VirtualService/ratings",
"line": 56,
"severity": "high",
"title": "ratings' only route aborts 20% of every request with fault injection",
"evidence": " - fault:\n value: 20\n httpStatus: 503",
"why": "The route has no match condition, so it matches every request to ratings, and the fault.abort injects a 503 20% of the time before the request ever reaches a ratings pod. Fault injection is a chaos-test tool; left in this production VirtualService it directly explains the errors jumping after the canary shipped.",
"fix": "apiVersion: networking.istio.io/v1\nkind: VirtualService\nmetadata:\n name: ratings\n namespace: bookinfo\nspec:\n hosts:\n - ratings\n http:\n - route:\n - destination:\n host: ratings\n subset: v1\n timeout: 2s\n retries:\n attempts: 3\n perTryTimeout: 1s\n retryOn: 5xx,connect-failure\n"
},
{
"id": "TR-02",
"flags": [
"F4"
],
"resource": "VirtualService/reviews",
"line": 16,
"severity": "medium",
"title": "canary-split weights total 95, not 100",
"evidence": " weight: 90\n weight: 5",
"why": "90 + 5 = 95. Depending on control-plane version this is either rejected at apply time or silently renormalized, so the live split is not guaranteed to be the 90/5 written here.",
"fix": "route:\n - destination:\n host: reviews\n subset: v1\n weight: 95\n - destination:\n host: reviews\n subset: v3\n weight: 5\n"
},
{
"id": "TR-04",
"flags": [
"F5"
],
"resource": "VirtualService/ratings",
"line": 65,
"severity": "medium",
"title": "ratings retry budget (4s) exceeds its 2s route timeout",
"evidence": " timeout: 2s\n attempts: 3\n perTryTimeout: 1s",
"why": "(1 + attempts) x perTryTimeout = (1 + 3) x 1s = 4s, which exceeds the 2s route timeout, so the overall timeout cuts retries off before all 3 can run - callers get fewer retries than configured, which compounds the errors from the fault injection above.",
"fix": "timeout: 4s\nretries:\n attempts: 3\n perTryTimeout: 1s\n retryOn: 5xx,connect-failure\n"
},
{
"id": "TR-07",
"flags": [
"F6"
],
"resource": "DestinationRule/ratings",
"line": 78,
"severity": "medium",
"title": "DestinationRule/ratings has no outlierDetection while its route retries",
"evidence": " trafficPolicy:\n connectionPool:\n http1MaxPendingRequests: 100",
"why": "VirtualService/ratings retries failed calls, but without outlierDetection a ratings pod that is failing keeps its full share of traffic, so retries can keep landing back on the same broken pod instead of being routed away from it.",
"fix": "trafficPolicy:\n connectionPool:\n http:\n http1MaxPendingRequests: 100\n outlierDetection:\n consecutive5xxErrors: 5\n interval: 30s\n baseEjectionTime: 30s\n maxEjectionPercent: 50\n"
},
{
"id": "TR-06",
"flags": [
"F7"
],
"resource": "VirtualService/reviews",
"line": 10,
"severity": "low",
"title": "both reviews http routes set no timeout",
"evidence": " http:\n - name: canary-split\n - name: testers",
"why": "Neither http[0] nor http[1] sets a timeout, and Istio applies none by default, so a hung reviews pod holds the caller's connection open indefinitely instead of failing fast.",
"fix": "http:\n - name: canary-split\n timeout: 5s\n route:\n - destination:\n host: reviews\n subset: v1\n weight: 90\n - destination:\n host: reviews\n subset: v3\n weight: 5\n - name: testers\n timeout: 5s\n match:\n - headers:\n end-user:\n exact: jason\n route:\n - destination:\n host: reviews\n subset: v2\n"
},
{
"id": "TR-07",
"flags": [
"F8"
],
"resource": "DestinationRule/reviews",
"line": 36,
"severity": "low",
"title": "DestinationRule/reviews has no outlierDetection",
"evidence": "spec:\n host: reviews\n subsets:",
"why": "No trafficPolicy is set at all for reviews, so a failing v1 or v2 pod is never ejected from the load balancing set. Reviews routes do not currently retry, so this is hygiene rather than an active failure mode today.",
"fix": "apiVersion: networking.istio.io/v1\nkind: DestinationRule\nmetadata:\n name: reviews\n namespace: bookinfo\nspec:\n host: reviews\n trafficPolicy:\n outlierDetection:\n consecutive5xxErrors: 5\n interval: 30s\n baseEjectionTime: 30s\n maxEjectionPercent: 50\n subsets:\n - name: v1\n labels:\n version: v1\n - name: v2\n labels:\n version: v2\n"
}
],
"flag_answers": [
{
"flag": "F1",
"status": "confirmed",
"reason": ""
},
{
"flag": "F2",
"status": "confirmed",
"reason": ""
},
{
"flag": "F3",
"status": "confirmed",
"reason": ""
},
{
"flag": "F4",
"status": "confirmed",
"reason": ""
},
{
"flag": "F5",
"status": "confirmed",
"reason": ""
},
{
"flag": "F6",
"status": "confirmed",
"reason": ""
},
{
"flag": "F7",
"status": "confirmed",
"reason": ""
},
{
"flag": "F8",
"status": "confirmed",
"reason": ""
}
],
"routes": [
{
"host": "reviews",
"behaviour": "Every request lands in http[0] (canary-split) because it has no match condition and is evaluated first: 90% goes to subset v1, and 5% is sent to subset v3, which DestinationRule/reviews does not define, so that 5% fails with 503. The weights only total 95, so the real split the control plane applies may not even be 90/5. http[1] (testers, matching header end-user: jason) never runs because the catch-all above it already matched - so jason's requests also fall into the 90/5 v1/v3 split and never reach v2. Neither route sets a timeout, so a stalled reviews pod would hold connections open indefinitely."
},
{
"host": "ratings",
"behaviour": "The single route has no match condition, so it applies to every request, and a fault.abort injects an HTTP 503 on 20% of them before ratings is even called. Surviving requests go to subset v1 with a 2s timeout and up to 3 retries at 1s each - but (1+3) x 1s = 4s is more than the 2s timeout, so the timeout cuts retries short before all 3 can run. DestinationRule/ratings has no outlierDetection, so none of this backs off a struggling v1 pod."
}
],
"plan": [
"Add subset v3 (labels version: v3) to DestinationRule/reviews so the 5% canary slice has a real destination instead of 503ing.",
"Remove the fault.abort from VirtualService/ratings's route - it is aborting 20% of all ratings traffic in production.",
"Reorder VirtualService/reviews so the testers match-based route runs before the canary-split catch-all.",
"Fix the canary-split weights to total 100 (e.g. 95/5).",
"Raise the ratings route timeout (or reduce its retry attempts) so the retry budget fits inside the timeout.",
"Add outlierDetection to DestinationRule/ratings and DestinationRule/reviews so a failing pod is ejected instead of keeping its share of traffic.",
"Add explicit timeouts to both reviews http routes."
],
"questions": [
"No Service or Deployment for reviews is in this paste - do pods labeled version: v3 already exist and are healthy? That confirms the subset I'm proposing to add to DestinationRule/reviews actually has somewhere to route to.",
"Was the intended canary split 90/10, 95/5, or something else? The pasted weights total 95; I assumed v3 keeps 5% and raised v1 to 95, but confirm if the real target differs.",
"Is the ratings fault injection left over from a chaos-test run that should already have been reverted, or is it meant to stay (behind a header match, say) for ongoing resilience testing?"
],
"summary": "Two separate bugs explain what you're seeing: VirtualService/reviews's canary-split route has no match condition and sits above testers, so every request including jason's is caught there first and never reaches the v2 rule - and that same route sends 5% of traffic to subset v3, which DestinationRule/reviews never defines, so that slice 503s outright. Ratings' errors are a different, second problem: its only route runs a 20% fault-abort on every request, and its 3x1s retry budget doesn't fit inside its 2s timeout, so retries get cut short too, and there's no outlier detection to route around a struggling pod. Fix the undefined v3 subset and pull the fault injection first since those are outright failures, then reorder the reviews routes and correct the weight math before you ship anything else on top of this canary."
}
Held to the page’s own reconciliation (recon.js, run by check.js on this exact body and reply), this reply has 0 disagreements in 26 statements checked.
What the step 4 samples print for this reply (after the truncated/charged line; the Python form):
unsafe - The reviews route's catch-all shadows the testers rule and sends 5% of traffic to an undefined v3 subset, while ratings runs a 20% fault-abort on every request and a retry budget that outruns its timeout.
TR-03 VirtualService/reviews 11 high
TR-01 VirtualService/reviews 19 high
TR-10 VirtualService/ratings 56 high
TR-02 VirtualService/reviews 16 medium
TR-04 VirtualService/ratings 65 medium
TR-07 DestinationRule/ratings 78 medium
TR-06 VirtualService/reviews 10 low
TR-07 DestinationRule/reviews 36 low
route reviews
route ratings
Had you pressed “Check mTLS for these hosts” after this reply, the page would send the
mtls lane on the same paste (the mtls facts of this config, the same note) with this
traffic_handoff, shown decoded; in the body it is the compact
JSON.stringify of it. That body is 5,546 characters and its key would be
mesh-desk:mtls:b7489ab-15aa:a1:
{
"from": "traffic",
"verdict": "unsafe",
"headline": "The reviews route's catch-all shadows the testers rule and sends 5% of traffic to an undefined v3 subset, while ratings runs a 20% fault-abort on every request and a retry budget that outruns its timeout.",
"hosts": [
"reviews",
"ratings"
],
"findings": [
{
"id": "TR-03",
"resource": "VirtualService/reviews",
"severity": "high",
"title": "canary-split has no match and sits above testers, so testers never runs"
},
{
"id": "TR-01",
"resource": "VirtualService/reviews",
"severity": "high",
"title": "canary-split routes 5% of reviews traffic to subset v3, which is not defined"
},
{
"id": "TR-10",
"resource": "VirtualService/ratings",
"severity": "high",
"title": "ratings' only route aborts 20% of every request with fault injection"
},
{
"id": "TR-02",
"resource": "VirtualService/reviews",
"severity": "medium",
"title": "canary-split weights total 95, not 100"
},
{
"id": "TR-04",
"resource": "VirtualService/ratings",
"severity": "medium",
"title": "ratings retry budget (4s) exceeds its 2s route timeout"
},
{
"id": "TR-07",
"resource": "DestinationRule/ratings",
"severity": "medium",
"title": "DestinationRule/ratings has no outlierDetection while its route retries"
},
{
"id": "TR-06",
"resource": "VirtualService/reviews",
"severity": "low",
"title": "both reviews http routes set no timeout"
},
{
"id": "TR-07",
"resource": "DestinationRule/reviews",
"severity": "low",
"title": "DestinationRule/reviews has no outlierDetection"
}
]
}
mTLS: A STRICT namespace with a plaintext port, a TLS-off DestinationRule and a DENY policy plaintext walks past
4 resources: PeerAuthentication/default, PeerAuthentication/legacy-batch, DestinationRule/ledger, AuthorizationPolicy/deny-sandbox. The page computes 4 posture rows — namespace/payments STRICT, workload/payments/app=legacy-batch STRICT (port 9090 DISABLE), effective/billing PERMISSIVE, effective/payments STRICT — and raises 3 flags (3 high) for the mTLS lane, reading the verdict as exposed. The engineer’s note: “Security review next week. We believe payments is fully mTLS and billing blocks the sandbox namespace. Are we right?”
The config string, 953 characters ending in a newline — save it as mtls-config.yaml:
# file: payments/peer-authentication.yaml
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: payments
spec:
mtls:
mode: STRICT
---
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: legacy-batch
namespace: payments
spec:
selector:
matchLabels:
app: legacy-batch
mtls:
mode: STRICT
portLevelMtls:
9090:
mode: DISABLE
---
# file: payments/ledger-dr.yaml
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: ledger
namespace: payments
spec:
host: ledger.payments.svc.cluster.local
trafficPolicy:
tls:
mode: DISABLE
---
# file: billing/deny-sandbox.yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: deny-sandbox
namespace: billing
spec:
selector:
matchLabels:
app: invoices
action: DENY
rules:
- from:
- source:
namespaces: ["sandbox"]
The facts object, decoded and complete — save it as mtls-facts.json:
{
"source": {
"lines": 53,
"format": "yaml",
"namespaces": [
"billing",
"payments"
],
"resources": [
{
"ref": "PeerAuthentication/default",
"kind": "PeerAuthentication",
"name": "default",
"namespace": "payments",
"api_version": "security.istio.io/v1",
"line": 2,
"end_line": 9
},
{
"ref": "PeerAuthentication/legacy-batch",
"kind": "PeerAuthentication",
"name": "legacy-batch",
"namespace": "payments",
"api_version": "security.istio.io/v1",
"line": 11,
"end_line": 24
},
{
"ref": "DestinationRule/ledger",
"kind": "DestinationRule",
"name": "ledger",
"namespace": "payments",
"api_version": "networking.istio.io/v1",
"line": 27,
"end_line": 36
},
{
"ref": "AuthorizationPolicy/deny-sandbox",
"kind": "AuthorizationPolicy",
"name": "deny-sandbox",
"namespace": "billing",
"api_version": "security.istio.io/v1",
"line": 39,
"end_line": 52
}
],
"parse_errors": []
},
"hosts": [
{
"host": "ledger.payments.svc.cluster.local",
"virtual_services": [],
"destination_rules": [
"DestinationRule/ledger"
],
"subsets_defined": [],
"subsets_routed": [],
"gateways": [],
"mtls_server": "STRICT",
"mtls_client": "DISABLE (DestinationRule/ledger, trafficPolicy)"
}
],
"posture": [
{
"scope": "namespace/payments",
"mode": "STRICT",
"source": "PeerAuthentication/default",
"ports": []
},
{
"scope": "workload/payments/app=legacy-batch",
"mode": "STRICT",
"source": "PeerAuthentication/legacy-batch",
"ports": [
{
"port": "9090",
"mode": "DISABLE"
}
]
},
{
"scope": "effective/billing",
"mode": "PERMISSIVE",
"source": "the Istio default - no PeerAuthentication pasted for namespace billing",
"ports": []
},
{
"scope": "effective/payments",
"mode": "STRICT",
"source": "PeerAuthentication/default",
"ports": []
}
],
"flags": [
{
"uid": "F2",
"id": "MT-04",
"sev": "high",
"resource": "PeerAuthentication/legacy-batch",
"line": 23,
"text": "Port 9090 of workload/payments/app=legacy-batch is DISABLE inside a STRICT policy",
"detail": "This port accepts only plaintext: a gap in an otherwise mutual-TLS workload."
},
{
"uid": "F3",
"id": "MT-06",
"sev": "high",
"resource": "DestinationRule/ledger",
"line": 35,
"text": "DestinationRule/ledger (trafficPolicy) sends plaintext to ledger.payments.svc.cluster.local, which requires mTLS (PeerAuthentication/default)",
"detail": "The server rejects plaintext, so every call from a client of this rule fails (connection reset, 503 UC)."
},
{
"uid": "F4",
"id": "MT-10",
"sev": "high",
"resource": "AuthorizationPolicy/deny-sandbox",
"line": 49,
"text": "AuthorizationPolicy/deny-sandbox DENYs by namespaces while the workloads accept plaintext (PERMISSIVE from the Istio default - no PeerAuthentication pasted for namespace billing)",
"detail": "A plaintext request has no peer identity, so a DENY on principals or namespaces never matches it: exactly the callers you meant to block can connect without a sidecar. Make the scope STRICT, or deny on notPrincipals."
}
],
"verdict_candidate": "exposed",
"notes": []
}
The request body, trimmed for display the same way (the full body is
4,196 characters). It has no traffic_handoff: this paste had no traffic run.
{
"task": "mtls",
"config": "# file: payments/peer-authentication.yaml\napiVersion: security.istio.io/v1\nkind: PeerAuthentication\nmetadata:\n name: default\n namespace: payments\nspec:\n mtls ... [trimmed for display: 160 of 953 characters shown]",
"facts": "{\"source\":{\"lines\":53,\"format\":\"yaml\",\"namespaces\":[\"billing\",\"payments\"],\"resources\":[{\"ref\":\"PeerAuthentication/default\",\"kind\":\"PeerAuthentication\",\"name\":\"d ... [trimmed for display: 160 of 2,733 characters shown]",
"note": "Security review next week. We believe payments is fully mTLS and billing blocks the sandbox namespace. Are we right?"
}
The page would send this with Idempotency-Key: mesh-desk:mtls:94b1ce2b-1064:a1. The saved reply, in full:
{
"task": "mtls",
"verdict": "exposed",
"headline": "Payments' namespace-wide STRICT mTLS is undercut by a plaintext port override on legacy-batch and a DestinationRule that disables TLS toward the STRICT ledger service, while billing's sandbox-blocking DENY is silently bypassed because billing has no mTLS policy at all.",
"posture": [
{
"scope": "namespace/payments",
"mode": "STRICT",
"source": "PeerAuthentication/default",
"comment": "Namespace-wide STRICT is in force, but it is undermined in practice by the legacy-batch port override (MT-04) and the ledger DestinationRule's plaintext client TLS (MT-06)."
},
{
"scope": "workload/payments/app=legacy-batch",
"mode": "STRICT",
"source": "PeerAuthentication/legacy-batch",
"comment": "The workload-level mode is STRICT, but portLevelMtls overrides port 9090 to DISABLE, opening a plaintext hole in an otherwise mTLS workload (MT-04)."
},
{
"scope": "effective/billing",
"mode": "PERMISSIVE",
"source": "the Istio default - no PeerAuthentication pasted for namespace billing",
"comment": "No PeerAuthentication exists for billing, so plaintext is accepted mesh-wide by default; the deny-sandbox AuthorizationPolicy's namespaces match cannot see identity from a plaintext caller (MT-10)."
},
{
"scope": "effective/payments",
"mode": "STRICT",
"source": "PeerAuthentication/default",
"comment": "Same STRICT posture as namespace/payments; see the two findings above for where it is still bypassed today."
}
],
"findings": [
{
"id": "MT-04",
"flags": [
"F2"
],
"resource": "PeerAuthentication/legacy-batch",
"line": 24,
"severity": "high",
"title": "Port 9090 override leaves legacy-batch in plaintext under a STRICT policy",
"evidence": "mode: STRICT\n9090:\n mode: DISABLE",
"why": "spec.mtls.mode is STRICT for workloads selected by app=legacy-batch, but spec.portLevelMtls overrides port 9090 specifically to DISABLE. Any client that skips its sidecar (or an attacker on the pod network) can reach port 9090 in plaintext with no certificate, even though the rest of this workload requires mTLS.",
"fix": "apiVersion: security.istio.io/v1\nkind: PeerAuthentication\nmetadata:\n name: legacy-batch\n namespace: payments\nspec:\n selector:\n matchLabels:\n app: legacy-batch\n mtls:\n mode: STRICT\n"
},
{
"id": "MT-06",
"flags": [
"F3"
],
"resource": "DestinationRule/ledger",
"line": 36,
"severity": "high",
"title": "DestinationRule/ledger forces plaintext toward a STRICT server",
"evidence": "host: ledger.payments.svc.cluster.local\nmode: DISABLE",
"why": "trafficPolicy.tls.mode: DISABLE tells every client that uses this DestinationRule to speak plaintext to ledger.payments.svc.cluster.local, but PeerAuthentication/default makes the payments namespace (and this host's server side) STRICT. The server-side sidecar will refuse the plaintext handshake, so every call routed through this rule fails outright (connection reset / 503 UC) - it is not a silent plaintext success.",
"fix": "apiVersion: networking.istio.io/v1\nkind: DestinationRule\nmetadata:\n name: ledger\n namespace: payments\nspec:\n host: ledger.payments.svc.cluster.local\n trafficPolicy:\n tls:\n mode: ISTIO_MUTUAL\n"
},
{
"id": "MT-10",
"flags": [
"F4"
],
"resource": "AuthorizationPolicy/deny-sandbox",
"line": 52,
"severity": "high",
"title": "deny-sandbox's DENY can't see identity from billing's plaintext callers",
"evidence": "action: DENY\nnamespaces: [\"sandbox\"]",
"why": "This policy denies callers whose verified source namespace is sandbox, but billing has no PeerAuthentication, so Istio's PERMISSIVE default lets callers connect without a sidecar or any peer identity. A request from sandbox that skips mTLS carries no namespace identity for Istio to match, so it never matches this DENY and reaches the invoices workload anyway - the exact traffic this policy was meant to block.",
"fix": "apiVersion: security.istio.io/v1\nkind: PeerAuthentication\nmetadata:\n name: default\n namespace: billing\nspec:\n mtls:\n mode: STRICT\n"
}
],
"flag_answers": [
{
"flag": "F2",
"status": "confirmed",
"reason": "Config shows mtls.mode: STRICT with portLevelMtls 9090 mode: DISABLE on PeerAuthentication/legacy-batch."
},
{
"flag": "F3",
"status": "confirmed",
"reason": "Config shows DestinationRule/ledger trafficPolicy.tls.mode: DISABLE for a host whose PeerAuthentication/default is STRICT."
},
{
"flag": "F4",
"status": "confirmed",
"reason": "Config shows AuthorizationPolicy/deny-sandbox DENYing on source.namespaces while billing has no PeerAuthentication (PERMISSIVE default)."
}
],
"rollout": [
"Confirm every legacy-batch client actually has an Istio sidecar (istioctl proxy-status / proxy-config), then remove the portLevelMtls override on PeerAuthentication/legacy-batch so port 9090 inherits the workload's STRICT mode.",
"Change DestinationRule/ledger's trafficPolicy.tls.mode from DISABLE to ISTIO_MUTUAL so its clients present mTLS certs that match the already-STRICT ledger.payments.svc.cluster.local server; verify with a test call before wider rollout.",
"Add a namespace-scoped PeerAuthentication for billing (e.g. billing/default). If any billing workload may still lack a sidecar, land it as PERMISSIVE first, confirm with istioctl that all workloads report mTLS traffic, then flip it to STRICT.",
"Once billing is STRICT, re-verify AuthorizationPolicy/deny-sandbox actually blocks a call from the sandbox namespace (istioctl authz check or a live test call) - only now does its namespaces match see verified identity.",
"Re-run the page's checks (or istioctl analyze) over the updated config to confirm no host regressed to plaintext and no new high finding was introduced."
],
"questions": [
"Is the port 9090 plaintext override on legacy-batch intentional (e.g. a legacy protocol that cannot speak TLS), or should it be brought under the namespace's STRICT policy?",
"Is DestinationRule/ledger's tls.mode: DISABLE a deliberate in-progress migration step, or should it already be ISTIO_MUTUAL now that PeerAuthentication/default is STRICT?",
"Are all workloads in the billing namespace confirmed to have Istio sidecars injected, so billing can go straight to STRICT rather than staging through PERMISSIVE first?"
],
"summary": "No on both counts. Payments is STRICT at the namespace level, but that is punched through twice: PeerAuthentication/legacy-batch leaves port 9090 in plaintext (MT-04), and DestinationRule/ledger forces its clients to speak plaintext to a host whose server side is STRICT, so those calls fail rather than succeed (MT-06). Billing does not block the sandbox namespace either: no PeerAuthentication is pasted for billing, so Istio's PERMISSIVE default applies, and the deny-sandbox DENY on source.namespaces never sees a peer identity from a plaintext caller - sandbox traffic bypasses it entirely (MT-10). Overall posture is exposed; see rollout for the order to close all three gaps without an outage."
}
Held to the page’s own reconciliation (recon.js, run by check.js on this exact body and reply), this reply has 0 disagreements in 15 statements checked.
What the step 5 samples print for this reply (the Python form):
exposed - Payments' namespace-wide STRICT mTLS is undercut by a plaintext port override on legacy-batch and a DestinationRule that disables TLS toward the STRICT ledger service, while billing's sandbox-blocking DENY is silently bypassed because billing has no mTLS policy at all.
posture namespace/payments STRICT
posture workload/payments/app=legacy-batch STRICT
posture effective/billing PERMISSIVE
posture effective/payments STRICT
MT-04 PeerAuthentication/legacy-batch 24 high
MT-06 DestinationRule/ledger 36 high
MT-10 AuthorizationPolicy/deny-sandbox 52 high
The page checks the reply against the config; the API does not
What comes back from the API is the model’s raw text in output.output. The page
holds it to account before showing it (recon.js reconcile, with the
facts, the config and the handoff it sent), and a direct API caller gets none of this:
- Every finding is anchored. Its
idis in the catalogue of the lane that was asked for; itsseverityis high, medium or low; itsresourceis a pasted resource; itsevidenceis found in that resource’s lines ofconfig(whitespace collapsed; quoted lines that are apart must appear in order), and alineoutside the resource is noted; every citeduidis a flag the page raised for this lane. - Every fix is checked, and whole-resource fixes are re-run. A fix must parse as YAML, contain no tabs and embed no private key; a medium or high finding without a fix is a disagreement. A fix that is a whole resource (or several) is put back into the config — replacing the resource of the same kind and name, or added when the paste lacks it — and the free check runs again: the fix must clear the flag it answers and introduce no new high flag.
- Every flag is answered once, consistently. A missing answer to a high, medium or low flag, a second answer, a
confirmedthat no finding carries, adismissedthat a finding confirms or that has no reason, is a disagreement. - The verdict fits. A high finding needs
unsafe/exposed, and that verdict needs a high finding;ready/strictallows no medium finding. - Traffic: every VirtualService host is described in
routes, and nothing else, each with abehaviour, and there is aplanwhenever there is something to fix. - mTLS: every posture scope is answered with the mode the page computed (a different mode needs a
comment), no scope is repeated or invented,strictis refused while any namespace is effectively PERMISSIVE, a non-strict posture needs arollout, and a traffic handoff’s hosts are covered by a posture row or a finding.
If you call the API directly, apply your own checks — the evidence against your config, every flag answered, the posture modes, each fix re-checked — before you apply anything to a cluster.
Step 1 — a tiny client helper and a token
One function per language that does what sdk.js does for every call: JSON in,
bearer token if you have one, data out on a 2xx, an error carrying
status, error.code and error.message otherwise. The later
steps assume this helper is in the same file (for Go, Java and C#, the later snippets go inside
main / the top-level statements; Java’s makeInput from step 3 goes
in the class).
Then a token. Two kinds work:
-
Personal — needed for a run. Both lanes are metered in credits, so a run
needs a signed-in user (the page says “The traffic run is metered, so it needs an
account.”) unless the estimate says
sponsor_enabled. Sign in and copy your token from /tokens.html, then export it asSKILLSAFE_TOKEN(the samples fall back to the placeholderYOUR_TOKEN). -
Guest — enough for
/meand/estimate.POST /guestwith{"slug": "mesh-desk"}and noAuthorizationheader, exactly assdk.jsguest()does. The response’sdatacarriestokenandguest_id. Reuse a token you already hold rather than minting a new one each time.
BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="${SKILLSAFE_TOKEN:-YOUR_TOKEN}" # see below
# api METHOD PATH [extra curl args...] -- prints the whole envelope; pipe to jq
api() {
method="$1"; path="$2"; shift 2
curl -sS -X "$method" "$BASE$path" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" "$@"
}
# success: jq '.data' failure: jq '.error | {code, message}'
# ---- get a token
# personal token: export SKILLSAFE_TOKEN=... (from /tokens.html) and skip this
# guest token: no Authorization header on this one call
TOKEN=$(curl -sS -X POST "$BASE/guest" \
-H "Content-Type: application/json" \
-d '{"slug":"mesh-desk"}' | jq -r '.data.token')
echo "guest token: ${TOKEN:0:12}..."
import hashlib, json, os, time, urllib.parse
import requests # pip install requests
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = os.environ.get("SKILLSAFE_TOKEN", "YOUR_TOKEN") # see below
class ApiError(Exception):
def __init__(self, status, code, message, details=None):
super().__init__(f"{status} {code}: {message}")
self.status, self.code, self.details = status, code, details
def api(method, path, body=None, headers=None):
h = {"Content-Type": "application/json"}
if TOKEN:
h["Authorization"] = "Bearer " + TOKEN
h.update(headers or {})
r = requests.request(method, BASE + path, headers=h,
data=None if body is None else json.dumps(body))
try:
env = r.json()
except ValueError:
env = {}
if not r.ok:
err = env.get("error") or {}
raise ApiError(r.status_code, err.get("code"), err.get("message") or r.reason, err.get("details"))
return env.get("data")
# ---- get a token
# personal token: set SKILLSAFE_TOKEN (from /tokens.html) and skip this
if TOKEN in ("", "YOUR_TOKEN"):
TOKEN = "" # no Authorization header on /guest
guest = api("POST", "/guest", {"slug": "mesh-desk"})
TOKEN = guest["token"]
print("guest", guest.get("guest_id"))
// mesh.mjs -- Node 18+ (global fetch), run with: node mesh.mjs
import { readFileSync } from "node:fs";
import { createHash } from "node:crypto";
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "YOUR_TOKEN"; // see below
async function api(method, path, body, headers = {}) {
const h = { "Content-Type": "application/json", ...headers };
if (TOKEN) h.Authorization = "Bearer " + TOKEN;
const res = await fetch(BASE + path, {
method,
headers: h,
body: body === undefined ? undefined : JSON.stringify(body),
});
const json = await res.json().catch(() => ({}));
if (!res.ok) {
const err = new Error((json.error && json.error.message) || res.statusText);
err.status = res.status;
err.code = json.error && json.error.code;
err.details = json.error && json.error.details;
throw err;
}
return json.data;
}
// ---- get a token
// personal token: paste it from /tokens.html into TOKEN and skip this
if (!TOKEN || TOKEN === "YOUR_TOKEN") {
TOKEN = ""; // no Authorization header on /guest
const guest = await api("POST", "/guest", { slug: "mesh-desk" });
TOKEN = guest.token;
console.log("guest", guest.guest_id);
}
package main
import (
"bufio"
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"time"
)
const base = "https://api.skillsafe.ai/v1/app-api"
var token = os.Getenv("SKILLSAFE_TOKEN") // see below
type apiError struct {
Status int
Code string
Message string
}
func (e *apiError) Error() string { return fmt.Sprintf("%d %s: %s", e.Status, e.Code, e.Message) }
type envelope struct {
OK bool `json:"ok"`
Data json.RawMessage `json:"data"`
Error *struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func newRequest(method, path string, body any, headers map[string]string) (*http.Request, error) {
var buf []byte
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return nil, err
}
buf = b
}
req, err := http.NewRequest(method, base+path, bytes.NewReader(buf))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
for k, v := range headers {
req.Header.Set(k, v)
}
return req, nil
}
func errorFrom(res *http.Response, env envelope) error {
e := &apiError{Status: res.StatusCode, Message: http.StatusText(res.StatusCode)}
if env.Error != nil {
e.Code = env.Error.Code
if env.Error.Message != "" {
e.Message = env.Error.Message
}
}
return e
}
// api sends body as JSON and decodes the envelope's data into out (out may be nil).
func api(method, path string, body any, headers map[string]string, out any) error {
req, err := newRequest(method, path, body, headers)
if err != nil {
return err
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
var env envelope
_ = json.NewDecoder(res.Body).Decode(&env)
if res.StatusCode < 200 || res.StatusCode > 299 {
return errorFrom(res, env)
}
if out != nil && len(env.Data) > 0 {
return json.Unmarshal(env.Data, out)
}
return nil
}
func main() {
// the token code below and steps 2-5 go here; remove any import a step you skip no longer uses
_, _, _, _, _ = bufio.NewScanner, sha256.Sum256, hex.EncodeToString, url.PathEscape, strings.Index
_ = time.Second
}
// ---- inside main: get a token
// personal token: set SKILLSAFE_TOKEN (from /tokens.html) and skip this
if token == "" {
var guest struct {
Token string `json:"token"`
GuestID string `json:"guest_id"`
}
if err := api("POST", "/guest", map[string]string{"slug": "mesh-desk"}, nil, &guest); err != nil {
panic(err)
}
token = guest.Token
fmt.Println("guest", guest.GuestID)
}
// MeshDesk.java -- Java 17+, needs com.fasterxml.jackson.core:jackson-databind on the classpath
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class MeshDesk {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = System.getenv().getOrDefault("SKILLSAFE_TOKEN", "YOUR_TOKEN"); // see below
static final HttpClient HTTP = HttpClient.newHttpClient();
static final ObjectMapper JSON = new ObjectMapper();
static class ApiException extends Exception {
final int status;
final String code;
ApiException(int status, String code, String message) {
super(status + " " + code + ": " + message);
this.status = status;
this.code = code;
}
}
static HttpRequest.Builder request(String method, String path, Object body, Map<String, String> headers) throws Exception {
HttpRequest.Builder b = HttpRequest.newBuilder(URI.create(BASE + path))
.header("Content-Type", "application/json")
.method(method, body == null ? HttpRequest.BodyPublishers.noBody()
: HttpRequest.BodyPublishers.ofString(JSON.writeValueAsString(body)));
if (token != null && !token.isEmpty()) b.header("Authorization", "Bearer " + token);
if (headers != null) headers.forEach(b::header);
return b;
}
static ApiException errorFrom(int status, JsonNode env) {
JsonNode err = env.path("error");
return new ApiException(status, err.path("code").asText(null), err.path("message").asText("HTTP " + status));
}
static JsonNode api(String method, String path, Object body, Map<String, String> headers) throws Exception {
HttpResponse<String> res = HTTP.send(request(method, path, body, headers).build(), HttpResponse.BodyHandlers.ofString());
JsonNode env;
try { env = JSON.readTree(res.body()); } catch (Exception e) { env = JSON.createObjectNode(); }
if (env == null) env = JSON.createObjectNode();
if (res.statusCode() / 100 != 2) throw errorFrom(res.statusCode(), env);
return env.path("data");
}
public static void main(String[] args) throws Exception {
// the token code below and steps 2-5 go here (makeInput from step 3 goes next to api())
}
}
// ---- inside main: get a token
// personal token: set SKILLSAFE_TOKEN (from /tokens.html) and skip this
if (token.isEmpty() || token.equals("YOUR_TOKEN")) {
token = ""; // no Authorization header on /guest
JsonNode guest = api("POST", "/guest", Map.of("slug", "mesh-desk"), null);
token = guest.path("token").asText();
System.out.println("guest " + guest.path("guest_id").asText());
}
require "digest"
require "json"
require "net/http"
require "uri"
BASE = "https://api.skillsafe.ai/v1/app-api"
$token = ENV.fetch("SKILLSAFE_TOKEN", "YOUR_TOKEN") # see below
class ApiError < StandardError
attr_reader :status, :code
def initialize(status, code, message)
super("#{status} #{code}: #{message}")
@status = status
@code = code
end
end
def build_request(method, path, body = nil, headers = {})
uri = URI(BASE + path)
req = Net::HTTPGenericRequest.new(method, !body.nil?, true, uri.request_uri)
req["Content-Type"] = "application/json"
req["Authorization"] = "Bearer #{$token}" if $token && !$token.empty?
headers.each { |k, v| req[k] = v }
req.body = JSON.generate(body) unless body.nil?
[uri, req]
end
def api(method, path, body = nil, headers = {})
uri, req = build_request(method, path, body, headers)
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
env = (JSON.parse(res.body) rescue {})
env = {} unless env.is_a?(Hash)
unless res.is_a?(Net::HTTPSuccess)
err = env["error"] || {}
raise ApiError.new(res.code.to_i, err["code"], err["message"] || res.message)
end
env["data"]
end
# ---- get a token
# personal token: set SKILLSAFE_TOKEN (from /tokens.html) and skip this
if $token.empty? || $token == "YOUR_TOKEN"
$token = "" # no Authorization header on /guest
guest = api("POST", "/guest", { slug: "mesh-desk" })
$token = guest["token"]
puts "guest #{guest["guest_id"]}"
end
<?php
// needs the curl extension
const BASE = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = getenv("SKILLSAFE_TOKEN") ?: "YOUR_TOKEN"; // see below
class ApiError extends Exception {
public $status;
public $apiCode;
public function __construct($status, $code, $message) {
parent::__construct("$status $code: $message");
$this->status = $status;
$this->apiCode = $code;
}
}
function headers_for(array $extra = []): array {
global $TOKEN;
$h = ["Content-Type: application/json"];
if ($TOKEN) $h[] = "Authorization: Bearer $TOKEN";
foreach ($extra as $k => $v) $h[] = "$k: $v";
return $h;
}
function api(string $method, string $path, $body = null, array $headers = []) {
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => headers_for($headers),
CURLOPT_RETURNTRANSFER => true,
]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
$raw = curl_exec($ch);
if ($raw === false) throw new RuntimeException(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$env = json_decode($raw);
if (!is_object($env)) $env = new stdClass();
if ($status < 200 || $status > 299) {
$e = $env->error ?? new stdClass();
throw new ApiError($status, $e->code ?? null, $e->message ?? "HTTP $status");
}
return $env->data ?? null;
}
// ---- get a token
// personal token: set SKILLSAFE_TOKEN (from /tokens.html) and skip this
if ($TOKEN === "" || $TOKEN === "YOUR_TOKEN") {
$TOKEN = ""; // no Authorization header on /guest
$guest = api("POST", "/guest", ["slug" => "mesh-desk"]);
$TOKEN = $guest->token;
echo "guest {$guest->guest_id}\n";
}
// Program.cs -- .NET 6+ console app with top-level statements; the token code and steps 2-5 (with the MakeInput local function) go ABOVE this class
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
static class Api
{
public const string Base = "https://api.skillsafe.ai/v1/app-api";
public static string Token = Environment.GetEnvironmentVariable("SKILLSAFE_TOKEN") ?? "YOUR_TOKEN"; // see below
public static readonly HttpClient Http = new HttpClient();
public class ApiException : Exception
{
public int Status;
public string? Code;
public ApiException(int status, string? code, string message) : base($"{status} {code}: {message}")
{
Status = status;
Code = code;
}
}
public static HttpRequestMessage Request(string method, string path, object? body = null, IDictionary<string, string>? headers = null)
{
var req = new HttpRequestMessage(new HttpMethod(method), Base + path);
if (body != null) req.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
if (!string.IsNullOrEmpty(Token)) req.Headers.TryAddWithoutValidation("Authorization", "Bearer " + Token);
if (headers != null) foreach (var kv in headers) req.Headers.TryAddWithoutValidation(kv.Key, kv.Value);
return req;
}
public static ApiException ErrorFrom(HttpResponseMessage res, JsonNode? env)
{
var err = env?["error"];
return new ApiException((int)res.StatusCode, err?["code"]?.GetValue<string>(),
err?["message"]?.GetValue<string>() ?? res.ReasonPhrase ?? "error");
}
public static async Task<JsonNode?> Call(string method, string path, object? body = null, IDictionary<string, string>? headers = null)
{
using var req = Request(method, path, body, headers);
using var res = await Http.SendAsync(req);
var text = await res.Content.ReadAsStringAsync();
JsonNode? env = null;
try { env = JsonNode.Parse(text); } catch (JsonException) { }
if (!res.IsSuccessStatusCode) throw ErrorFrom(res, env);
return env?["data"];
}
}
// ---- top-level statements (above the Api class): get a token
// personal token: set SKILLSAFE_TOKEN (from /tokens.html) and skip this
if (Api.Token == "" || Api.Token == "YOUR_TOKEN")
{
Api.Token = ""; // no Authorization header on /guest
var guest = await Api.Call("POST", "/guest", new { slug = "mesh-desk" });
Api.Token = guest!["token"]!.GetValue<string>();
Console.WriteLine($"guest {guest["guest_id"]}");
}
Step 2 — GET /me
Who the token belongs to and what it can spend. sdk.js documents the shape as
{subject_type, subject_id, credits, profile?}. The page treats the caller as signed
in only when subject_type is "user", shows credits as the
balance (1 credit = $0.0001, so it divides by 10 000 for the dollar figure), and compares it
with the estimate in step 3 before it lets you run.
api GET /me | jq '.data // .error'
me = api("GET", "/me")
print(me["subject_type"], me.get("subject_id"), me.get("credits"))
signed_in = me["subject_type"] == "user"
const me = await api("GET", "/me");
console.log(me.subject_type, me.subject_id, me.credits);
const signedIn = me.subject_type === "user";
var me struct {
SubjectType string `json:"subject_type"`
SubjectID any `json:"subject_id"`
Credits any `json:"credits"`
}
if err := api("GET", "/me", nil, nil, &me); err != nil {
panic(err)
}
fmt.Println(me.SubjectType, me.SubjectID, me.Credits)
signedIn := me.SubjectType == "user"
_ = signedIn
JsonNode me = api("GET", "/me", null, null);
System.out.println(me.path("subject_type").asText() + " " + me.path("subject_id").asText() + " " + me.path("credits"));
boolean signedIn = me.path("subject_type").asText().equals("user");
me = api("GET", "/me")
puts [me["subject_type"], me["subject_id"], me["credits"]].join(" ")
signed_in = me["subject_type"] == "user"
$me = api("GET", "/me");
echo $me->subject_type, " ", $me->subject_id ?? "", " ", $me->credits ?? "", "\n";
$signedIn = $me->subject_type === "user";
var me = await Api.Call("GET", "/me");
Console.WriteLine($"{me?["subject_type"]} {me?["subject_id"]} {me?["credits"]}");
var signedIn = me?["subject_type"]?.GetValue<string>() == "user";
Step 3 — build the input, then POST /estimate
First a small makeInput that does what buildInput does: task,
then config as the YAML text, facts as a compact JSON string, the note
when it is not blank, and — for the mtls lane only — the traffic_handoff
string when you have one. The samples build the traffic input from the two blocks
of the traffic worked example; step 5 reuses makeInput for the mtls one. Then price
it. The body of /estimate is the same input you will send to /run; no job
is created and nothing is charged — /estimate is free. From the response the page
reads:
| Field | Meaning |
|---|---|
hold_credits | A reservation, not a price: the worst case the platform holds while the run is in flight. The page shows it as “reserves N credits ($...) for the traffic run” and adds that you are charged only for what the run actually uses. What you pay is the job’s charged_credits after settlement (step 4), normally less; the rest of the hold is released. A balance below the hold is allowed but the reply may be cut short (truncated: true). |
min_credits | The smallest balance that can start a run. Below it the page disables the run button, because the call would be a 402. |
sponsor_enabled | When true, a caller who is not signed in may run; otherwise the page asks them to sign in. |
warnings | Input warnings (for example a missing required field). They do not refuse the call; the page shows them next to the price, so check them before you run. |
The page re-prices whenever the paste, the note or the lane change. The amounts move with the
model’s rates and grow with the size of config and facts, so read
them from the call rather than hard-coding them.
# traffic-config.yaml = the config block of the traffic worked example, saved as is (with its final
# newline); traffic-facts.json = its facts block. --rawfile keeps the YAML byte for byte (jq 1.6+),
# jq -c turns the facts into ONE compact string, note is optional (the page trims it, max 2000 chars).
BODY=$(jq -n --rawfile config traffic-config.yaml --arg facts "$(jq -c . traffic-facts.json)" \
--arg note "We shipped reviews v3 as a 5% canary an hour ago. The testers (end-user: jason) say they never see v2, and ratings errors jumped. What is wrong before I apply the next change?" \
'{task: "traffic", config: $config, facts: $facts, note: $note}')
# mtls lane only, after a traffic run on the SAME paste: add --arg handoff "$(jq -c . handoff.json)"
# and traffic_handoff: $handoff to the object.
api POST /estimate -d "$BODY" \
| jq '.error // (.data | {hold_credits, min_credits, sponsor_enabled, warnings, model, model_alias, markup_bps})'
def make_input(task, config_path, facts_path, note=None, handoff=None):
"""The body the page sends: task, config (the YAML text), facts as a JSON STRING, optional note,
and (mtls lane only) the traffic_handoff string."""
with open(config_path, encoding="utf-8", newline="") as f:
config = f.read() # the YAML, verbatim
with open(facts_path, encoding="utf-8") as f:
facts = json.load(f) # the page's free check for this lane
inp = {
"task": task,
"config": config,
"facts": json.dumps(facts, separators=(",", ":"), ensure_ascii=False), # a STRING, like JSON.stringify
}
n = (note or "").strip()
if n:
inp["note"] = n[:2000] # optional; the page caps it at 2000 chars
if task == "mtls" and handoff:
inp["traffic_handoff"] = handoff # optional; a JSON string built from a traffic reply
return inp
inp = make_input("traffic", "traffic-config.yaml", "traffic-facts.json",
"We shipped reviews v3 as a 5% canary an hour ago. The testers (end-user: jason) say they never see v2, and ratings errors jumped. What is wrong before I apply the next change?")
est = api("POST", "/estimate", inp)
print("hold", est.get("hold_credits"), "min", est.get("min_credits"),
"sponsored", est.get("sponsor_enabled"), "warnings", est.get("warnings") or [])
// The body the page sends: task, config (the YAML text), facts as a JSON STRING, an optional note,
// and (mtls lane only) the traffic_handoff string.
function makeInput(task, configPath, factsPath, note, handoff) {
const input = {
task,
config: readFileSync(configPath, "utf8"), // the YAML, verbatim
facts: JSON.stringify(JSON.parse(readFileSync(factsPath, "utf8"))), // a STRING: the page's free check
};
const n = (note || "").trim();
if (n) input.note = n.length > 2000 ? n.slice(0, 2000) : n; // optional; max 2000 chars
if (task === "mtls" && handoff) input.traffic_handoff = handoff; // optional; a JSON string
return input;
}
const input = makeInput("traffic", "traffic-config.yaml", "traffic-facts.json",
"We shipped reviews v3 as a 5% canary an hour ago. The testers (end-user: jason) say they never see v2, and ratings errors jumped. What is wrong before I apply the next change?");
const est = await api("POST", "/estimate", input);
console.log("hold", est.hold_credits, "min", est.min_credits,
"sponsored", est.sponsor_enabled, "warnings", est.warnings || []);
// makeInput builds the body the page sends: task, config (the YAML text), facts as a JSON STRING,
// an optional note and (mtls lane only) the traffic_handoff string.
makeInput := func(task, configPath, factsPath, note, handoff string) map[string]any {
read := func(path string) []byte {
raw, err := os.ReadFile(path)
if err != nil {
panic(err)
}
return raw
}
var facts bytes.Buffer
if err := json.Compact(&facts, read(factsPath)); err != nil { // keeps key order; travels as a STRING
panic(err)
}
in := map[string]any{"task": task, "config": string(read(configPath)), "facts": facts.String()}
if n := []rune(strings.TrimSpace(note)); len(n) > 0 {
if len(n) > 2000 {
n = n[:2000] // optional; the page caps it at 2000 chars
}
in["note"] = string(n)
}
if task == "mtls" && handoff != "" {
in["traffic_handoff"] = handoff // optional; a JSON string built from a traffic reply
}
return in
}
input := makeInput("traffic", "traffic-config.yaml", "traffic-facts.json",
"We shipped reviews v3 as a 5% canary an hour ago. The testers (end-user: jason) say they never see v2, and ratings errors jumped. What is wrong before I apply the next change?", "")
var est struct {
HoldCredits any `json:"hold_credits"`
MinCredits any `json:"min_credits"`
SponsorEnabled bool `json:"sponsor_enabled"`
Warnings []any `json:"warnings"`
}
if err := api("POST", "/estimate", input, nil, &est); err != nil {
panic(err)
}
fmt.Printf("hold %v min %v sponsored %v warnings %v\n", est.HoldCredits, est.MinCredits, est.SponsorEnabled, est.Warnings)
// ---- add to the MeshDesk class, next to api(): the body the page sends
static Map<String, Object> makeInput(String task, String configPath, String factsPath, String note, String handoff) throws Exception {
Map<String, Object> in = new LinkedHashMap<>();
in.put("task", task);
in.put("config", Files.readString(Path.of(configPath))); // the YAML, verbatim
in.put("facts", JSON.writeValueAsString(JSON.readTree(Files.readString(Path.of(factsPath))))); // a STRING, compact
String n = note == null ? "" : note.strip();
if (!n.isEmpty()) in.put("note", n.length() > 2000 ? n.substring(0, 2000) : n); // optional; max 2000 chars
if (task.equals("mtls") && handoff != null && !handoff.isEmpty()) in.put("traffic_handoff", handoff); // optional
return in;
}
// ---- inside main
Map<String, Object> input = makeInput("traffic", "traffic-config.yaml", "traffic-facts.json",
"We shipped reviews v3 as a 5% canary an hour ago. The testers (end-user: jason) say they never see v2, and ratings errors jumped. What is wrong before I apply the next change?", null);
JsonNode est = api("POST", "/estimate", input, null);
System.out.println("hold " + est.path("hold_credits") + " min " + est.path("min_credits")
+ " sponsored " + est.path("sponsor_enabled").asBoolean(false) + " warnings " + est.path("warnings"));
# The body the page sends: task, config (the YAML text), facts as a JSON STRING, an optional note,
# and (mtls lane only) the traffic_handoff string.
def make_input(task, config_path, facts_path, note = nil, handoff = nil)
input = {
task: task,
config: File.read(config_path, encoding: "UTF-8"), # the YAML, verbatim
facts: JSON.generate(JSON.parse(File.read(facts_path, encoding: "UTF-8"))) # a STRING, compact
}
n = note.to_s.strip
input[:note] = n[0, 2000] unless n.empty? # optional; max 2000 chars
input[:traffic_handoff] = handoff if task == "mtls" && handoff && !handoff.empty? # optional
input
end
input = make_input("traffic", "traffic-config.yaml", "traffic-facts.json",
"We shipped reviews v3 as a 5% canary an hour ago. The testers (end-user: jason) say they never see v2, and ratings errors jumped. What is wrong before I apply the next change?")
est = api("POST", "/estimate", input)
puts "hold #{est["hold_credits"]} min #{est["min_credits"]} " \
"sponsored #{est["sponsor_enabled"]} warnings #{(est["warnings"] || []).inspect}"
// The body the page sends: task, config (the YAML text), facts as a JSON STRING, an optional note,
// and (mtls lane only) the traffic_handoff string.
function make_input(string $task, string $configPath, string $factsPath, string $note = "", string $handoff = ""): array {
// decode to objects (not arrays) so an empty {} stays {} when re-encoded
$facts = json_encode(json_decode(file_get_contents($factsPath)), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$input = ["task" => $task, "config" => file_get_contents($configPath), "facts" => $facts]; // all STRINGS
$n = trim($note);
if ($n !== "") $input["note"] = mb_substr($n, 0, 2000); // optional; max 2000 chars
if ($task === "mtls" && $handoff !== "") $input["traffic_handoff"] = $handoff; // optional
return $input;
}
$input = make_input("traffic", "traffic-config.yaml", "traffic-facts.json",
"We shipped reviews v3 as a 5% canary an hour ago. The testers (end-user: jason) say they never see v2, and ratings errors jumped. What is wrong before I apply the next change?");
$est = api("POST", "/estimate", $input);
echo "hold ", $est->hold_credits ?? "-", " min ", $est->min_credits ?? "-",
" sponsored ", json_encode($est->sponsor_enabled ?? false),
" warnings ", json_encode($est->warnings ?? []), "\n";
// The body the page sends: task, config (the YAML text), facts as a JSON STRING, an optional note,
// and (mtls lane only) the traffic_handoff string.
static Dictionary<string, string> MakeInput(string task, string configPath, string factsPath, string? note = null, string? handoff = null)
{
var compact = new JsonSerializerOptions { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
var input = new Dictionary<string, string>
{
["task"] = task,
["config"] = File.ReadAllText(configPath), // the YAML, verbatim
["facts"] = JsonNode.Parse(File.ReadAllText(factsPath))!.ToJsonString(compact), // a STRING, compact
};
var n = (note ?? "").Trim();
if (n.Length > 0) input["note"] = n.Length > 2000 ? n[..2000] : n; // optional; max 2000 chars
if (task == "mtls" && !string.IsNullOrEmpty(handoff)) input["traffic_handoff"] = handoff; // optional
return input;
}
var input = MakeInput("traffic", "traffic-config.yaml", "traffic-facts.json",
"We shipped reviews v3 as a 5% canary an hour ago. The testers (end-user: jason) say they never see v2, and ratings errors jumped. What is wrong before I apply the next change?");
var est = await Api.Call("POST", "/estimate", input);
Console.WriteLine($"hold {est?["hold_credits"]} min {est?["min_credits"]} " +
$"sponsored {est?["sponsor_enabled"]} warnings {est?["warnings"]?.ToJsonString() ?? "[]"}");
Step 4 — POST /run, then poll the job (traffic)
Send the same input to /run with an Idempotency-Key header, using a
personal token. Its data carries a job_id. Poll
GET /jobs/{job_id} the way sdk.js waitForJob does: every
second until status is succeeded or failed, giving up on
the client side after 180 seconds. On the finished job, read:
output.output— the model’s reply as a string: the JSON object from the output contract of the lane you asked for. Parse it yourself (from the first{to the last}). The samples run the traffic input and print the verdict and headline, each finding’sid,resource,lineandseverity, then each routedhost.truncated—truewhen the reply was cut short because the balance ran out. The page then closes the cut-off JSON, shows what arrived as a partial result (“N of 8 sections recovered”), and says it was cut short by the available balance.charged_credits— the real cost of the run, as opposed to the estimate’shold_creditsreservation. The page re-reads/meafterwards for the new balance.error— on afailedjob.
Idempotency key. Resending a request with the same key must not start a second
paid run, so derive the key from the input, not from the clock — and include the lane, so a
traffic audit and an mTLS check of the same paste never share a key. The page uses
mesh-desk:<lane>:<inputHash>:a<attempt>, where
<lane> is traffic or mtls,
<inputHash> is a 32-bit djb2 hash of JSON.stringify(input) in hex, a
dash, and the string’s length in hex (app.js inputHash, over the
input without retry_note), and <attempt> is 1
for a normal run and 2 for the automatic reformat retry (which also adds
retry_note). For the two worked examples that is mesh-desk:traffic:d50ba79b-1ac3:a1 and
mesh-desk:mtls:94b1ce2b-1064:a1 (their reformat retries would use mesh-desk:traffic:d50ba79b-1ac3:a2 and mesh-desk:mtls:94b1ce2b-1064:a2). The
format is the page’s convention, not a requirement: any stable, unique string does. The
samples below use the lane and a SHA-256 prefix.
The page itself uses /run-stream (step 5), not /run; both take the
same body and header.
KEY="mesh-desk:traffic:$(printf '%s' "$BODY" | shasum -a 256 | cut -c1-16):a1"
JOB=$(api POST /run -H "Idempotency-Key: $KEY" -d "$BODY" | jq -r '.data.job_id')
for i in $(seq 180); do # 1 s interval, 180 s client-side limit
J=$(api GET "/jobs/$JOB")
S=$(printf '%s' "$J" | jq -r '.data.status')
if [ "$S" = "succeeded" ] || [ "$S" = "failed" ]; then break; fi
sleep 1
done
printf '%s' "$J" | jq '.data | {status, truncated, charged_credits, error}'
printf '%s' "$J" | jq -r '.data.output.output' | jq '{verdict, headline, findings: [(.findings // [])[] | {id, resource, line, severity}], routes: [(.routes // [])[] | .host]}'
body = json.dumps(inp)
key = "mesh-desk:traffic:" + hashlib.sha256(body.encode()).hexdigest()[:16] + ":a1"
job_id = api("POST", "/run", inp, {"Idempotency-Key": key})["job_id"]
deadline = time.time() + 180 # 1 s interval, 180 s client-side limit
while True:
job = api("GET", "/jobs/" + urllib.parse.quote(job_id, safe=""))
if job["status"] in ("succeeded", "failed"):
break
if time.time() > deadline:
raise TimeoutError("job timed out")
time.sleep(1)
if job["status"] == "failed":
raise SystemExit(f"run failed: {job.get('error')}")
print("truncated:", job.get("truncated") is True, "charged:", job.get("charged_credits"))
text = (job.get("output") or {}).get("output") or ""
reply = json.loads(text[text.index("{"): text.rindex("}") + 1])
print(reply["verdict"], "-", reply["headline"])
for f in reply.get("findings", []):
print(f["id"], f["resource"], f.get("line"), f["severity"])
for rt in reply.get("routes", []):
print("route", rt["host"])
const key = "mesh-desk:traffic:" +
createHash("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 16) + ":a1";
const { job_id } = await api("POST", "/run", input, { "Idempotency-Key": key });
let job;
const deadline = Date.now() + 180000; // 1 s interval, 180 s client-side limit
for (;;) {
job = await api("GET", "/jobs/" + encodeURIComponent(job_id));
if (job.status === "succeeded" || job.status === "failed") break;
if (Date.now() > deadline) throw new Error("job timed out");
await new Promise((r) => setTimeout(r, 1000));
}
if (job.status === "failed") throw new Error("run failed: " + JSON.stringify(job.error));
console.log("truncated:", job.truncated === true, "charged:", job.charged_credits);
const text = (job.output && job.output.output) || "";
const reply = JSON.parse(text.slice(text.indexOf("{"), text.lastIndexOf("}") + 1));
console.log(reply.verdict, "-", reply.headline);
for (const f of reply.findings || []) console.log(f.id, f.resource, f.line, f.severity);
for (const rt of reply.routes || []) console.log("route", rt.host);
inputJSON, _ := json.Marshal(input)
sum := sha256.Sum256(inputJSON)
key := "mesh-desk:traffic:" + hex.EncodeToString(sum[:8]) + ":a1"
var started struct {
JobID string `json:"job_id"`
}
if err := api("POST", "/run", input, map[string]string{"Idempotency-Key": key}, &started); err != nil {
panic(err)
}
type jobT struct {
Status string `json:"status"`
Output *struct {
Output string `json:"output"`
} `json:"output"`
Truncated bool `json:"truncated"`
ChargedCredits any `json:"charged_credits"`
Error any `json:"error"`
}
var job jobT
deadline := time.Now().Add(180 * time.Second) // 1 s interval, 180 s client-side limit
for {
job = jobT{}
if err := api("GET", "/jobs/"+url.PathEscape(started.JobID), nil, nil, &job); err != nil {
panic(err)
}
if job.Status == "succeeded" || job.Status == "failed" {
break
}
if time.Now().After(deadline) {
panic("job timed out")
}
time.Sleep(time.Second)
}
if job.Status == "failed" {
panic(fmt.Sprintf("run failed: %v", job.Error))
}
fmt.Println("truncated:", job.Truncated, "charged:", job.ChargedCredits)
text := ""
if job.Output != nil {
text = job.Output.Output
}
var reply map[string]any
if a, b := strings.Index(text, "{"), strings.LastIndex(text, "}"); a >= 0 && b > a {
if err := json.Unmarshal([]byte(text[a:b+1]), &reply); err != nil {
panic(err)
}
}
fmt.Println(reply["verdict"], "-", reply["headline"])
findings, _ := reply["findings"].([]any)
for _, e := range findings {
f, _ := e.(map[string]any)
fmt.Println(f["id"], f["resource"], f["line"], f["severity"])
}
routes, _ := reply["routes"].([]any)
for _, e := range routes {
rt, _ := e.(map[string]any)
fmt.Println("route", rt["host"])
}
byte[] digest = MessageDigest.getInstance("SHA-256").digest(JSON.writeValueAsBytes(input));
String key = "mesh-desk:traffic:" + HexFormat.of().formatHex(digest, 0, 8) + ":a1";
String jobId = api("POST", "/run", input, Map.of("Idempotency-Key", key)).path("job_id").asText();
JsonNode job;
long deadline = System.currentTimeMillis() + 180_000; // 1 s interval, 180 s client-side limit
while (true) {
job = api("GET", "/jobs/" + URLEncoder.encode(jobId, StandardCharsets.UTF_8), null, null);
String s = job.path("status").asText();
if (s.equals("succeeded") || s.equals("failed")) break;
if (System.currentTimeMillis() > deadline) throw new RuntimeException("job timed out");
Thread.sleep(1000);
}
if (job.path("status").asText().equals("failed")) throw new RuntimeException("run failed: " + job.path("error"));
System.out.println("truncated: " + job.path("truncated").asBoolean(false) + ", charged: " + job.path("charged_credits"));
String text = job.path("output").path("output").asText("");
JsonNode reply = JSON.readTree(text.substring(text.indexOf('{'), text.lastIndexOf('}') + 1));
System.out.println(reply.path("verdict").asText() + " - " + reply.path("headline").asText());
for (JsonNode f : reply.path("findings")) {
System.out.println(f.path("id").asText() + " " + f.path("resource").asText() + " " + f.path("line") + " " + f.path("severity").asText());
}
for (JsonNode rt : reply.path("routes")) {
System.out.println("route" + " " + rt.path("host").asText());
}
key = "mesh-desk:traffic:#{Digest::SHA256.hexdigest(JSON.generate(input))[0, 16]}:a1"
job_id = api("POST", "/run", input, { "Idempotency-Key" => key })["job_id"]
deadline = Time.now + 180 # 1 s interval, 180 s client-side limit
job = nil
loop do
job = api("GET", "/jobs/#{URI.encode_www_form_component(job_id)}")
break if %w[succeeded failed].include?(job["status"])
raise "job timed out" if Time.now > deadline
sleep 1
end
raise "run failed: #{job["error"].inspect}" if job["status"] == "failed"
puts "truncated: #{job["truncated"] == true}, charged: #{job["charged_credits"]}"
text = (job["output"] || {})["output"].to_s
reply = JSON.parse(text[text.index("{")..text.rindex("}")])
puts "#{reply["verdict"]} - #{reply["headline"]}"
(reply["findings"] || []).each { |f| puts "#{f["id"]} #{f["resource"]} #{f["line"].inspect} #{f["severity"]}" }
(reply["routes"] || []).each { |rt| puts "route #{rt["host"]}" }
$key = "mesh-desk:traffic:" . substr(hash("sha256", json_encode($input)), 0, 16) . ":a1";
$jobId = api("POST", "/run", $input, ["Idempotency-Key" => $key])->job_id;
$deadline = time() + 180; // 1 s interval, 180 s client-side limit
while (true) {
$job = api("GET", "/jobs/" . rawurlencode($jobId));
if (in_array($job->status, ["succeeded", "failed"], true)) break;
if (time() > $deadline) throw new RuntimeException("job timed out");
sleep(1);
}
if ($job->status === "failed") throw new RuntimeException("run failed: " . json_encode($job->error ?? null));
echo "truncated: ", json_encode(($job->truncated ?? false) === true), ", charged: ", $job->charged_credits ?? "-", "\n";
$text = $job->output->output ?? "";
$reply = json_decode(substr($text, strpos($text, "{"), strrpos($text, "}") - strpos($text, "{") + 1));
echo $reply->verdict, " - ", $reply->headline, "\n";
foreach ($reply->findings ?? [] as $f) echo $f->id, " ", $f->resource, " ", json_encode($f->line ?? null), " ", $f->severity, "\n";
foreach ($reply->routes ?? [] as $rt) echo "route", " ", $rt->host, "\n";
var digest = System.Security.Cryptography.SHA256.HashData(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(input)));
var key = "mesh-desk:traffic:" + Convert.ToHexString(digest, 0, 8).ToLowerInvariant() + ":a1";
var started = await Api.Call("POST", "/run", input, new Dictionary<string, string> { ["Idempotency-Key"] = key });
var jobId = started!["job_id"]!.GetValue<string>();
JsonNode job;
var deadline = DateTime.UtcNow.AddSeconds(180); // 1 s interval, 180 s client-side limit
while (true)
{
job = (await Api.Call("GET", "/jobs/" + Uri.EscapeDataString(jobId)))!;
var s = job["status"]?.GetValue<string>();
if (s == "succeeded" || s == "failed") break;
if (DateTime.UtcNow > deadline) throw new TimeoutException("job timed out");
await Task.Delay(1000);
}
if (job["status"]?.GetValue<string>() == "failed") throw new Exception($"run failed: {job["error"]?.ToJsonString()}");
var truncated = job["truncated"] is JsonValue t && t.TryGetValue(out bool tb) && tb;
Console.WriteLine($"truncated: {truncated}, charged: {job["charged_credits"]}");
var text = job["output"]?["output"]?.GetValue<string>() ?? "";
var reply = JsonNode.Parse(text[text.IndexOf('{')..(text.LastIndexOf('}') + 1)])!;
Console.WriteLine($"{reply["verdict"]} - {reply["headline"]}");
foreach (var f in reply["findings"]?.AsArray() ?? new JsonArray())
Console.WriteLine($"{f?["id"]} {f?["resource"]} {f?["line"]?.ToJsonString() ?? "null"} {f?["severity"]}");
foreach (var rt in reply["routes"]?.AsArray() ?? new JsonArray())
Console.WriteLine($"route {rt?["host"]}");
Step 5 — POST /run-stream (Server-Sent Events, mtls)
The alternative to step 4, and what the page uses: same kind of body, same
Idempotency-Key header, one request. Run one or the other for a given input, not
both. To show the second lane, the samples build the mtls input from the mtls
worked example (with its own key) and stream it; to stream the traffic input instead, pass the
step 3 input and key. The response is text/event-stream. sdk.js splits
it into frames on a blank line, takes the event name from an event: line (default
message) and concatenates the data: lines, skips frames with no data or
with data that is not JSON, and handles these events:
| Event | Data | What the SDK does |
|---|---|---|
delta | {text} | Passes text to onDelta. |
job | job info | Passes it to onJob (the page uses it to advance its progress display). |
done | the final payload: {job_id, status, charged_credits, output} | Keeps it as the result. |
pending | a payload | Treated exactly like done. If it carries a job_id and no output, poll that job as in step 4. |
error | {message, code, job_id} | After the stream ends, raises an error with those fields. |
If the response is not text/event-stream, it is an ordinary JSON envelope:
the SDK returns its data (an idempotent replay returns this way) or raises the
error on a non-2xx status. Handle both shapes.
From the final payload the page reads output.output (falling back to the
concatenated delta text), truncated and charged_credits.
Browsers receive progress ticks rather than deltas from this endpoint, so the page relies on the
final payload; if the stream fails part-way, it tries to recover a partial reply from whatever
delta text arrived. The samples print the verdict and headline, each posture row’s
scope and mode, then each finding’s id,
resource, line and severity. To run the mtls lane after a
traffic run on the same paste, pass the handoff string as makeInput’s last
argument.
# the mtls worked example: mtls-config.yaml and mtls-facts.json
MBODY=$(jq -n --rawfile config mtls-config.yaml --arg facts "$(jq -c . mtls-facts.json)" \
--arg note "Security review next week. We believe payments is fully mTLS and billing blocks the sandbox namespace. Are we right?" \
'{task: "mtls", config: $config, facts: $facts, note: $note}')
MKEY="mesh-desk:mtls:$(printf '%s' "$MBODY" | shasum -a 256 | cut -c1-16):a1"
# -N turns off buffering so frames print as they arrive
api POST /run-stream -N -H "Idempotency-Key: $MKEY" -d "$MBODY"
# frames look like:
# event: job
# data: {...}
#
# event: done
# data: {"job_id":"...","status":"...","charged_credits":...,"output":{"output":"{\"task\":\"mtls\",...}"}}
# keep only the final reply text:
api POST /run-stream -N -H "Idempotency-Key: $MKEY" -d "$MBODY" \
| awk '/^event:/{ev=$2} /^data:/ && ev=="done"{sub(/^data: */,""); print}' \
| jq -r '.output.output' | jq '{verdict, headline, posture: [(.posture // [])[] | {scope, mode}], findings: [(.findings // [])[] | {id, resource, line, severity}]}'
# the mtls worked example: mtls-config.yaml and mtls-facts.json (add handoff=... after a traffic run on the same paste)
mtls_inp = make_input("mtls", "mtls-config.yaml", "mtls-facts.json",
"Security review next week. We believe payments is fully mTLS and billing blocks the sandbox namespace. Are we right?")
mtls_key = "mesh-desk:mtls:" + hashlib.sha256(json.dumps(mtls_inp).encode()).hexdigest()[:16] + ":a1"
h = {"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN, "Idempotency-Key": mtls_key}
done, failure, streamed = None, None, ""
with requests.post(BASE + "/run-stream", headers=h, data=json.dumps(mtls_inp), stream=True) as r:
if "text/event-stream" not in r.headers.get("content-type", ""):
try:
env = r.json()
except ValueError:
env = {}
if not r.ok:
err = env.get("error") or {}
raise ApiError(r.status_code, err.get("code"), err.get("message") or r.reason)
done = env.get("data")
else:
event, data = "message", ""
for line in r.iter_lines(decode_unicode=True):
if line == "": # a blank line ends a frame
if data:
try:
payload = json.loads(data)
except ValueError:
payload = None
if event == "delta" and payload is not None:
streamed += payload.get("text") or ""
elif event == "job":
print("job", payload)
elif event in ("done", "pending"):
done = payload
elif event == "error":
failure = payload
event, data = "message", ""
elif line.startswith("event:"):
event = line[6:].strip()
elif line.startswith("data:"):
data += line[5:].strip()
if failure:
raise SystemExit(f"{failure.get('code')}: {failure.get('message')} (job {failure.get('job_id')})")
done = done or {}
full = (done.get("output") or {}).get("output") or streamed
print("truncated:", done.get("truncated") is True, "charged:", done.get("charged_credits"))
result = json.loads(full[full.index("{"): full.rindex("}") + 1])
print(result["verdict"], "-", result["headline"])
for ps in result.get("posture", []):
print("posture", ps["scope"], ps["mode"])
for f in result.get("findings", []):
print(f["id"], f["resource"], f.get("line"), f["severity"])
// the mtls worked example: mtls-config.yaml and mtls-facts.json (pass a handoff after a traffic run on the same paste)
const mtlsInput = makeInput("mtls", "mtls-config.yaml", "mtls-facts.json",
"Security review next week. We believe payments is fully mTLS and billing blocks the sandbox namespace. Are we right?");
const mtlsKey = "mesh-desk:mtls:" +
createHash("sha256").update(JSON.stringify(mtlsInput)).digest("hex").slice(0, 16) + ":a1";
const res = await fetch(BASE + "/run-stream", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: "Bearer " + TOKEN, "Idempotency-Key": mtlsKey },
body: JSON.stringify(mtlsInput),
});
let done = null, failure = null, streamed = "";
if (!(res.headers.get("content-type") || "").includes("text/event-stream")) {
const json = await res.json().catch(() => ({}));
if (!res.ok) throw new Error((json.error && json.error.message) || res.statusText);
done = json.data;
} else {
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
for (;;) {
const chunk = await reader.read();
if (chunk.done) break;
buffer += decoder.decode(chunk.value, { stream: true });
let idx;
while ((idx = buffer.indexOf("\n\n")) >= 0) {
const frame = buffer.slice(0, idx);
buffer = buffer.slice(idx + 2);
let event = "message", dataStr = "";
for (const line of frame.split("\n")) {
if (line.startsWith("event:")) event = line.slice(6).trim();
else if (line.startsWith("data:")) dataStr += line.slice(5).trim();
}
if (!dataStr) continue;
let data;
try { data = JSON.parse(dataStr); } catch { continue; }
if (event === "delta") streamed += data.text || "";
else if (event === "job") console.log("job", data);
else if (event === "done" || event === "pending") done = data;
else if (event === "error") failure = data;
}
}
}
if (failure) throw new Error(`${failure.code}: ${failure.message} (job ${failure.job_id})`);
const full = (done && done.output && done.output.output) || streamed;
console.log("truncated:", !!(done && done.truncated), "charged:", done && done.charged_credits);
const result = JSON.parse(full.slice(full.indexOf("{"), full.lastIndexOf("}") + 1));
console.log(result.verdict, "-", result.headline);
for (const ps of result.posture || []) console.log("posture", ps.scope, ps.mode);
for (const f of result.findings || []) console.log(f.id, f.resource, f.line, f.severity);
// the mtls worked example: mtls-config.yaml and mtls-facts.json (pass a handoff after a traffic run on the same paste)
mtlsInput := makeInput("mtls", "mtls-config.yaml", "mtls-facts.json",
"Security review next week. We believe payments is fully mTLS and billing blocks the sandbox namespace. Are we right?", "")
mtlsJSON, _ := json.Marshal(mtlsInput)
mtlsSum := sha256.Sum256(mtlsJSON)
mtlsKey := "mesh-desk:mtls:" + hex.EncodeToString(mtlsSum[:8]) + ":a1"
sreq, err := newRequest("POST", "/run-stream", mtlsInput, map[string]string{"Idempotency-Key": mtlsKey})
if err != nil {
panic(err)
}
sres, err := http.DefaultClient.Do(sreq)
if err != nil {
panic(err)
}
defer sres.Body.Close()
var done, failure map[string]any
var streamed strings.Builder
if !strings.Contains(sres.Header.Get("Content-Type"), "text/event-stream") {
var env envelope
_ = json.NewDecoder(sres.Body).Decode(&env)
if sres.StatusCode < 200 || sres.StatusCode > 299 {
panic(errorFrom(sres, env))
}
_ = json.Unmarshal(env.Data, &done)
} else {
sc := bufio.NewScanner(sres.Body)
sc.Buffer(make([]byte, 64*1024), 16*1024*1024) // the done frame can be long
event, data := "message", ""
for sc.Scan() {
line := sc.Text()
switch {
case line == "": // a blank line ends a frame
if data != "" {
var p map[string]any
if json.Unmarshal([]byte(data), &p) == nil {
switch event {
case "delta":
if t, ok := p["text"].(string); ok {
streamed.WriteString(t)
}
case "job":
fmt.Println("job", p)
case "done", "pending":
done = p
case "error":
failure = p
}
}
}
event, data = "message", ""
case strings.HasPrefix(line, "event:"):
event = strings.TrimSpace(line[6:])
case strings.HasPrefix(line, "data:"):
data += strings.TrimSpace(line[5:])
}
}
}
if failure != nil {
panic(fmt.Sprintf("%v: %v (job %v)", failure["code"], failure["message"], failure["job_id"]))
}
full := streamed.String()
if out, ok := done["output"].(map[string]any); ok {
if s, ok := out["output"].(string); ok && s != "" {
full = s
}
}
fmt.Println("truncated:", done["truncated"] == true, "charged:", done["charged_credits"])
var result map[string]any
if a, b := strings.Index(full, "{"), strings.LastIndex(full, "}"); a >= 0 && b > a {
if err := json.Unmarshal([]byte(full[a:b+1]), &result); err != nil {
panic(err)
}
}
fmt.Println(result["verdict"], "-", result["headline"])
resultPosture, _ := result["posture"].([]any)
for _, e := range resultPosture {
ps, _ := e.(map[string]any)
fmt.Println("posture", ps["scope"], ps["mode"])
}
resultFindings, _ := result["findings"].([]any)
for _, e := range resultFindings {
f, _ := e.(map[string]any)
fmt.Println(f["id"], f["resource"], f["line"], f["severity"])
}
// the mtls worked example: mtls-config.yaml and mtls-facts.json (pass a handoff after a traffic run on the same paste)
Map<String, Object> mtlsInput = makeInput("mtls", "mtls-config.yaml", "mtls-facts.json",
"Security review next week. We believe payments is fully mTLS and billing blocks the sandbox namespace. Are we right?", null);
byte[] mtlsDigest = MessageDigest.getInstance("SHA-256").digest(JSON.writeValueAsBytes(mtlsInput));
String mtlsKey = "mesh-desk:mtls:" + HexFormat.of().formatHex(mtlsDigest, 0, 8) + ":a1";
HttpRequest sreq = request("POST", "/run-stream", mtlsInput, Map.of("Idempotency-Key", mtlsKey)).build();
HttpResponse<Stream<String>> sres = HTTP.send(sreq, HttpResponse.BodyHandlers.ofLines());
JsonNode done = null, failure = null;
StringBuilder streamed = new StringBuilder();
if (!sres.headers().firstValue("content-type").orElse("").contains("text/event-stream")) {
JsonNode env = JSON.readTree(sres.body().collect(Collectors.joining("\n")));
if (env == null) env = JSON.createObjectNode();
if (sres.statusCode() / 100 != 2) throw errorFrom(sres.statusCode(), env);
done = env.path("data");
} else {
String event = "message";
StringBuilder data = new StringBuilder();
Iterator<String> it = sres.body().iterator();
while (it.hasNext()) {
String line = it.next();
if (line.isEmpty()) { // a blank line ends a frame
if (data.length() > 0) {
JsonNode p = null;
try { p = JSON.readTree(data.toString()); } catch (Exception ignored) { }
if (p != null) {
switch (event) {
case "delta": streamed.append(p.path("text").asText("")); break;
case "job": System.out.println("job " + p); break;
case "done": case "pending": done = p; break;
case "error": failure = p; break;
default: break;
}
}
}
event = "message";
data.setLength(0);
} else if (line.startsWith("event:")) event = line.substring(6).trim();
else if (line.startsWith("data:")) data.append(line.substring(5).trim());
}
}
if (failure != null) {
throw new RuntimeException(failure.path("code").asText() + ": " + failure.path("message").asText()
+ " (job " + failure.path("job_id").asText() + ")");
}
String full = done == null ? "" : done.path("output").path("output").asText("");
if (full.isEmpty()) full = streamed.toString();
System.out.println("truncated: " + (done != null && done.path("truncated").asBoolean(false))
+ ", charged: " + (done == null ? "-" : done.path("charged_credits").toString()));
JsonNode result = JSON.readTree(full.substring(full.indexOf('{'), full.lastIndexOf('}') + 1));
System.out.println(result.path("verdict").asText() + " - " + result.path("headline").asText());
for (JsonNode ps : result.path("posture")) {
System.out.println("posture" + " " + ps.path("scope").asText() + " " + ps.path("mode").asText());
}
for (JsonNode f : result.path("findings")) {
System.out.println(f.path("id").asText() + " " + f.path("resource").asText() + " " + f.path("line") + " " + f.path("severity").asText());
}
# the mtls worked example: mtls-config.yaml and mtls-facts.json (pass a handoff after a traffic run on the same paste)
mtls_input = make_input("mtls", "mtls-config.yaml", "mtls-facts.json",
"Security review next week. We believe payments is fully mTLS and billing blocks the sandbox namespace. Are we right?")
mtls_key = "mesh-desk:mtls:#{Digest::SHA256.hexdigest(JSON.generate(mtls_input))[0, 16]}:a1"
uri, req = build_request("POST", "/run-stream", mtls_input, { "Idempotency-Key" => mtls_key })
done = failure = nil
streamed = +""
Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: 300) do |http|
http.request(req) do |res|
unless res["content-type"].to_s.include?("text/event-stream")
env = (JSON.parse(res.body) rescue {})
unless res.is_a?(Net::HTTPSuccess)
raise ApiError.new(res.code.to_i, env.dig("error", "code"), env.dig("error", "message") || res.message)
end
done = env["data"]
next
end
buf = +""
res.read_body do |chunk|
buf << chunk
while (i = buf.index("\n\n"))
frame = buf.slice!(0, i + 2)
event = "message"
data = +""
frame.split("\n").each do |line|
if line.start_with?("event:") then event = line[6..].strip
elsif line.start_with?("data:") then data << line[5..].strip
end
end
next if data.empty?
payload = (JSON.parse(data) rescue nil)
next if payload.nil?
case event
when "delta" then streamed << payload["text"].to_s
when "job" then puts "job #{payload.inspect}"
when "done", "pending" then done = payload
when "error" then failure = payload
end
end
end
end
end
raise "#{failure["code"]}: #{failure["message"]} (job #{failure["job_id"]})" if failure
done ||= {}
full = (done["output"] || {})["output"].to_s
full = streamed if full.empty?
puts "truncated: #{done["truncated"] == true}, charged: #{done["charged_credits"]}"
result = JSON.parse(full[full.index("{")..full.rindex("}")])
puts "#{result["verdict"]} - #{result["headline"]}"
(result["posture"] || []).each { |ps| puts "posture #{ps["scope"]} #{ps["mode"]}" }
(result["findings"] || []).each { |f| puts "#{f["id"]} #{f["resource"]} #{f["line"].inspect} #{f["severity"]}" }
// the mtls worked example: mtls-config.yaml and mtls-facts.json (pass a handoff after a traffic run on the same paste)
$mtlsInput = make_input("mtls", "mtls-config.yaml", "mtls-facts.json",
"Security review next week. We believe payments is fully mTLS and billing blocks the sandbox namespace. Are we right?");
$mtlsKey = "mesh-desk:mtls:" . substr(hash("sha256", json_encode($mtlsInput)), 0, 16) . ":a1";
$done = null; $failure = null; $streamed = ""; $buf = ""; $ctype = "";
$handle = function (string $event, string $data) use (&$done, &$failure, &$streamed) {
$p = json_decode($data);
if ($p === null) return; // not JSON: skipped, as sdk.js does
switch ($event) {
case "delta": $streamed .= $p->text ?? ""; break;
case "job": echo "job ", json_encode($p), "\n"; break;
case "done": case "pending": $done = $p; break;
case "error": $failure = $p; break;
}
};
$ch = curl_init(BASE . "/run-stream");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => headers_for(["Idempotency-Key" => $mtlsKey]),
CURLOPT_POSTFIELDS => json_encode($mtlsInput, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$ctype) {
if (stripos($line, "content-type:") === 0) $ctype = strtolower(trim(substr($line, 13)));
return strlen($line);
},
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) use (&$buf, &$ctype, $handle) {
$buf .= $chunk;
if (strpos($ctype, "text/event-stream") === false) return strlen($chunk); // plain JSON: parse at the end
while (($i = strpos($buf, "\n\n")) !== false) {
$frame = substr($buf, 0, $i);
$buf = substr($buf, $i + 2);
$event = "message"; $data = "";
foreach (explode("\n", $frame) as $line) {
if (strpos($line, "event:") === 0) $event = trim(substr($line, 6));
elseif (strpos($line, "data:") === 0) $data .= trim(substr($line, 5));
}
if ($data !== "") $handle($event, $data);
}
return strlen($chunk);
},
]);
if (curl_exec($ch) === false) throw new RuntimeException(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if (strpos($ctype, "text/event-stream") === false) {
$env = json_decode($buf) ?? new stdClass();
if ($status < 200 || $status > 299) {
throw new ApiError($status, $env->error->code ?? null, $env->error->message ?? "HTTP $status");
}
$done = $env->data ?? null;
}
if ($failure) throw new RuntimeException(($failure->code ?? "") . ": " . ($failure->message ?? "") . " (job " . ($failure->job_id ?? "") . ")");
$full = $done->output->output ?? "";
if ($full === "") $full = $streamed;
echo "truncated: ", json_encode(($done->truncated ?? false) === true), ", charged: ", $done->charged_credits ?? "-", "\n";
$result = json_decode(substr($full, strpos($full, "{"), strrpos($full, "}") - strpos($full, "{") + 1));
echo $result->verdict, " - ", $result->headline, "\n";
foreach ($result->posture ?? [] as $ps) echo "posture", " ", $ps->scope, " ", $ps->mode, "\n";
foreach ($result->findings ?? [] as $f) echo $f->id, " ", $f->resource, " ", json_encode($f->line ?? null), " ", $f->severity, "\n";
// the mtls worked example: mtls-config.yaml and mtls-facts.json (pass a handoff after a traffic run on the same paste)
var mtlsInput = MakeInput("mtls", "mtls-config.yaml", "mtls-facts.json",
"Security review next week. We believe payments is fully mTLS and billing blocks the sandbox namespace. Are we right?");
var mtlsDigest = System.Security.Cryptography.SHA256.HashData(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(mtlsInput)));
var mtlsKey = "mesh-desk:mtls:" + Convert.ToHexString(mtlsDigest, 0, 8).ToLowerInvariant() + ":a1";
using var sreq = Api.Request("POST", "/run-stream", mtlsInput, new Dictionary<string, string> { ["Idempotency-Key"] = mtlsKey });
using var sres = await Api.Http.SendAsync(sreq, HttpCompletionOption.ResponseHeadersRead);
JsonNode? done = null, failure = null;
var streamed = new StringBuilder();
if (sres.Content.Headers.ContentType?.MediaType != "text/event-stream")
{
JsonNode? env = null;
try { env = JsonNode.Parse(await sres.Content.ReadAsStringAsync()); } catch (JsonException) { }
if (!sres.IsSuccessStatusCode) throw Api.ErrorFrom(sres, env);
done = env?["data"];
}
else
{
using var reader = new StreamReader(await sres.Content.ReadAsStreamAsync());
var ev = "message";
var data = new StringBuilder();
string? line;
while ((line = await reader.ReadLineAsync()) != null)
{
if (line.Length == 0) // a blank line ends a frame
{
if (data.Length > 0)
{
JsonNode? p = null;
try { p = JsonNode.Parse(data.ToString()); } catch (JsonException) { }
if (p != null)
{
switch (ev)
{
case "delta": streamed.Append(p["text"]?.GetValue<string>() ?? ""); break;
case "job": Console.WriteLine($"job {p.ToJsonString()}"); break;
case "done": case "pending": done = p; break;
case "error": failure = p; break;
}
}
}
ev = "message";
data.Clear();
}
else if (line.StartsWith("event:")) ev = line[6..].Trim();
else if (line.StartsWith("data:")) data.Append(line[5..].Trim());
}
}
if (failure != null) throw new Exception($"{failure["code"]}: {failure["message"]} (job {failure["job_id"]})");
var full = done?["output"]?["output"]?.GetValue<string>();
if (string.IsNullOrEmpty(full)) full = streamed.ToString();
var cut = done?["truncated"] is JsonValue tv && tv.TryGetValue(out bool cutShort) && cutShort;
Console.WriteLine($"truncated: {cut}, charged: {done?["charged_credits"]}");
var result = JsonNode.Parse(full[full.IndexOf('{')..(full.LastIndexOf('}') + 1)])!;
Console.WriteLine($"{result["verdict"]} - {result["headline"]}");
foreach (var ps in result["posture"]?.AsArray() ?? new JsonArray())
Console.WriteLine($"posture {ps?["scope"]} {ps?["mode"]}");
foreach (var f in result["findings"]?.AsArray() ?? new JsonArray())
Console.WriteLine($"{f?["id"]} {f?["resource"]} {f?["line"]?.ToJsonString() ?? "null"} {f?["severity"]}");
What the page does that you now do
- Reads the paste in the browser and builds
configand the lane’sfactsfrom it (meshkit.js), cutting a large paste on whole documents; you send the same two strings. - Prices every input with
/estimateand refuses to run belowmin_credits. - Sends each attempt with an input- and lane-derived
Idempotency-Key, and retries once withretry_notewhen a reply does not parse. - Closes a cut-off JSON reply and shows what arrived when
truncatedis true or the stream ends early. - Hands a traffic reply to the mtls lane as
traffic_handoff, only while the paste is unchanged. - Checks the reply against the config (
recon.js): resources, lines, evidence, fixes re-run through the free check, flag answers, verdict, routes for traffic and posture modes for mTLS.
Or skip all of this and use Mesh Desk in the browser: the check, the flags, the routing table, the mTLS posture and the Markdown and CSV exports are free.