Run Query Gate from your own code
Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page ranks the architecture by scan economics and the pricing shapes, audits every view for engine-level isolation and every compute handle for its caps, and checks the schema contract and credentials, then hands it all to the run as facts. The run writes the access blueprint or the customer contract; the page then holds it to its own facts (qgreply.js).
Base URL and the response shape
Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response has one shape:
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }
The token is minted for this app (the guest call takes {"slug":"query-gate"}), so no slug header is needed afterwards. Send it as Authorization: Bearer YOUR_TOKEN. The run body is the input object, sent bare.
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The body is not a JSON object, or a required key (task, product, surface, facts) is missing. The page always sends all five of task, product, surface, carry, facts; carry is an empty string for blueprint. |
| 401 | unauthorized | No token, or a stale one. Mint a guest token or sign in again. |
| 402 | insufficient_credits | The balance is under min_credits. Price with /estimate first. |
| 403 | forbidden | A guest token tried to run: both lanes are metered and need a personal sign-in. |
| 404 | not_found | Unknown job id. |
| 429 | rate_limited | Back off and retry. |
| 5xx | server_error | Transient. Retry with the same Idempotency-Key so a retry never double-bills. |
The task field comes first
Two lanes, chosen by task, in the order the work runs - for the team building it, then for the customers connecting:
blueprint- the access design: the architecture and when to move rungs, the engine policy per view, the caps per handle, connectivity, credentials and the pricing shape.contract- what customers read: the four-part data contract, what counts as breaking, every planned breaking change staged, and a connection page per client.
A missing or unknown task is answered as blueprint. Both lanes send the same five fields. The facts are the page's checks, one row per line, and a line starting "must:" is a rule the reply has to hold. The page's handoff carries the blueprint's decisions into carry for the contract lane.
The input
| Field | Required | What |
|---|---|---|
task | yes | blueprint or contract. If it is missing or neither, do blueprint and set "task": "blueprint". |
product | yes | the product and the demand, the problem, where the data lives, tenants, table size and scan frequency, consumers and clients, schema and CI, connectivity, credentials and pricing intent; "not given" means not given. |
surface | yes | every view with its isolation mechanism, identity and probe; every compute handle with its caps and who pays; every planned schema change. |
carry | send it (empty for blueprint) | contract task only: the blueprint's decisions, when the blueprint task ran first; may be empty. |
facts | yes | the page's checks, one line each as area | id | status | evidence, the verdict and the rankings. A line starting "must:" applies to your reply. |
retry_note | no | optional; if present, your previous reply was unusable - follow it. |
The rules the model applies
From the source skill, @samber/sql-jdbc-access-design - the only standard the run applies:
- The stakes are asymmetric: an API bug returns a wrong response; an isolation bug on a live SQL handle returns another customer's rows, and a runaway customer query lands on your infrastructure bill. Isolation and cost caps must both pass before any customer gets a handle.
- Design for the most demanding consumer present - BI analysts need a certified driver and stable columns, data engineers push the largest queries, embedded apps and agents issue unreviewed machine queries - and gate the architecture on scan frequency, never on who bought the product. A request for periodic files or a fed warehouse is bulk delivery, not this surface.
- Architecture: a zero-copy warehouse share is the default when the data already lives in a shared warehouse and scans are infrequent (cheap, fresh, paid per query scan); a replicated copy or read endpoint when scan frequency inverts the arithmetic (one practitioner's heuristic: about 20 scans a day on a 200 GB table makes live re-scanning cost more per month than a copy per year), at the price of staleness and a second copy to govern; per-tenant compute isolation when arbitrary customer SQL runs at dashboard or agent frequency across many tenants, or the shared cluster's worst query already threatens the product. An OLTP-only stack deletes the share rung; a hard ship date promotes what the platform gives you today.
- Isolation lives in the engine, never in application code: an application-layer tenant filter is one missed WHERE away from leaking rows. On a share, expose only secure or dynamic views joined to a private entitlement table and filtered on the share-aware identity (current_account, current_recipient) - current_role and current_user go NULL across a share. Partition filtering is an optimization, never access control. On Postgres-family engines use row-level security with FORCE, with the tenant set server-side from a verified token. Probe as another tenant and require zero foreign rows, after every policy change. Schema-per-tenant collapses past a few hundred tenants.
- The schema is an API: serve a curated mart, never the live operational schema; add freely, never rename, retype or drop in place - add, deprecate, migrate, remove; enforce it in CI; write the contract in four parts - schema, quality, freshness, availability.
- Connectivity is a certification tax: a Postgres wire-protocol endpoint gets every BI tool, psql and ORM for free; your own driver means accurate SQLSTATEs, signed connectors that expire and support routed back to you; a custom dialect starts every integration from zero. Publish a connection page per client: URL template, driver, TLS stated as required, auth method.
- Governance: none of the cost controls are on by default. Every external compute handle gets a statement timeout, a concurrency cap and a spend cap; a reader account or any provider-paid compute with no spend cap bills you without limit. A tenant that keeps tripping caps moves to its own compute; never raise the shared cap for everyone.
- Credentials: short-lived tokens before managed secrets before static passwords; key-pair JWTs for services, OAuth with PKCE for BI tools, degrading to a key-pair or token service account where a tool cannot refresh; never credentials inline in a connection string; scoped to the tenant, TLS required, self-service revocation.
- Pricing: a paid tier or add-on, never a free feature. The flat add-on or tier gate is the default; consumption or volume tiers when your own cost is consumption-based or customer volumes span orders of magnitude, metered on a business unit the buyer understands, never warehouse credits; provider-pays passthrough arrives with reader accounts and needs a spend cap on every account. A compounding mandate promotes the meter early; a ship date holds the flat gate.
House rules
- Use only the input. Never invent a customer, a view, a handle, a figure, a vendor fact or a date - except a value a field below asks you to propose (a cap, an availability target), always labelled as proposed.
- The page's checks, rankings and orders are settled: never move a rung, rank differently or call a failed gate fine.
- No web links (http or https); connection URL templates are expected, but never with a credential inside them.
- Write in the language of the input; keep the JSON keys, the rung, shape and connectivity ids, and view and handle names as given.
- Never use backticks and never write the input's field names as labels in your text. Words and terms of art from the rules above are free to use; what you never do is paste a whole sentence or quoted saying from this prompt, or state a rule with no fact of this product in it.
- Reply with ONLY one JSON object - no prose before or after, no code fences.
The replies
blueprint
Write the SQL access design: the state the surface must reach before customers connect - where the facts show a failing view or handle, write the policy or cap that replaces it, not today's broken state (the failures belong in caveats).
summary- one sentence, at most 40 words: the architecture, and what must hold before any customer gets a handle.problem-scope(one sentence: which problem this is, for this product),consumers(short strings: each consumer and its query shape).architecture-rung(share,replicaorisolated, exactly as the facts rank first),promote_when(one sentence: the measurable condition in this product's terms that would move it up the ladder (share, then replica, then per-tenant isolation) to the next rung - or, when the rung is already per-tenant isolation, the condition under which a cheaper rung would do),why(one sentence naming the moves that ranked it).isolation- one object per view, exactly the facts' list and order:view(as written),policy(one sentence: the engine policy this view must have - a secure or dynamic view or row-level security),identity(the identity the policy filters on),probe(one sentence: the probe run as another tenant, which must return zero foreign rows).schema-layer(one sentence: the curated mart this product serves),rule(one sentence: the additive-only rule as it applies to these tables),ci(one sentence: how this product's CI blocks a breaking change).connectivity-rung(postgres,driverordialect, exactly as the facts read it),setup(one sentence: what this product publishes so its clients connect).governance- one object per compute handle, exactly the facts' list and order:handle(as written),timeout,concurrency,spend(each a short phrase giving the cap to set on this handle - a proposed starting value where the input gives none, said to be proposed; never none).credentials-order(short strings, best first),bi(one sentence: how this product's BI clients authenticate),service(one sentence: how its services and agents authenticate).pricing-shape(flat,consumptionorpassthrough, exactly as the facts rank first),meter(the business unit metered, empty for a flat gate),why(one sentence).caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "blueprint",
"summary": "one sentence",
"problem": { "scope": "one sentence", "consumers": [ "a consumer and its query shape" ] },
"architecture": { "rung": "share", "promote_when": "one sentence", "why": "one sentence" },
"isolation": [ { "view": "as written", "policy": "one sentence", "identity": "the identity", "probe": "one sentence" } ],
"schema": { "layer": "one sentence", "rule": "one sentence", "ci": "one sentence" },
"connectivity": { "rung": "postgres", "setup": "one sentence" },
"governance": [ { "handle": "as written", "timeout": "a cap", "concurrency": "a cap", "spend": "a cap" } ],
"credentials": { "order": [ "best first" ], "bi": "one sentence", "service": "one sentence" },
"pricing": { "shape": "flat", "meter": "", "why": "one sentence" },
"caveats": [ ]
}
The page holds it to its own facts (check() in qgreply.js; any warning makes the reply flagged): the reply answers the task it was sent; no web link; no backticks; no credential inside a connection string; a summary of at most 40 words; the page's architecture rung with a promotion condition; exactly the page's views in order, each with an engine policy (never an application-layer filter), the right identity (share-aware on a warehouse, a verified token otherwise) and a probe run as another tenant; exactly the page's handles in order, each with a timeout, a concurrency cap and a spend cap; the page's connectivity rung; short-lived tokens first; the page's pricing shape, metered on a business unit and never warehouse credits; a curated mart, the additive-only rule and CI enforcement; at most four caveats.
contract
Write what customers read: the data contract and one connection page per client.
summary- one sentence, at most 40 words.contract-schema(the columns, types and nullability customers may rely on),quality(the completeness thresholds the data meets),freshness(the refresh window - the input's interval when it gives one),availability(an uptime figure and a latency figure, each said to be proposed when the input gives none) - one or two sentences each, for this product's tables.breaking- short strings: what counts as a breaking change - a rename, a type change, a drop - in this product's terms; an example may name this product's columns, but never describe a change the input shows as staged as if it were made in place.changes- one object per planned breaking change, exactly the facts' list and order:change(the change as the facts name it),steps(four short strings in order: add, deprecate, migrate, remove - each saying what happens for this change; for a drop with no replacement, the add step says nothing is added).pages- one object per client, exactly the facts' list and order:client(as written),url(the connection URL template, no credential in it; host and database as placeholders unless the input names them; protocol defaults such as the standard port and a TLS-required flag are fine),driver(the driver or connector),tls(TLS stated as required),auth(a token, OAuth or key-pair method - never a password).caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "contract",
"summary": "one sentence",
"contract": { "schema": "one or two sentences", "quality": "one or two sentences", "freshness": "one or two sentences", "availability": "one or two sentences" },
"breaking": [ "a rename of a column or table" ],
"changes": [ { "change": "as the facts name it", "steps": [ "add ...", "deprecate ...", "migrate ...", "remove ..." ] } ],
"pages": [ { "client": "as written", "url": "a template", "driver": "a driver", "tls": "TLS required", "auth": "a method" } ],
"caveats": [ ]
}
The page requires: the reply answers the task it was sent; the same text rules; all four contract parts, with the refresh interval when the page has one; a rename, a type change and a drop named as breaking; exactly the page's planned breaking changes in order, each with the four steps add, deprecate, migrate, remove; exactly the page's clients in order, each page with a URL template, a driver, TLS stated as required and token, OAuth or key-pair auth - never a password.
Worked examples
Three runs from the page's free samples (invented companies): the blueprint for an analytics SaaS filtering in app code, the contract for the same product carrying that blueprint, and the blueprint for a billing SaaS exposing its live Postgres. Each input is exactly what the page sends; each reply is the saved reply the sample replays, shipped unedited - but the facts and long strings are shortened in this display only, and every cut is listed under the example. The last line is the page's own check of the full reply.
blueprint - An analytics SaaS filtering in app code
The page read it as: no customer gets a handle yet; architectures share, isolated, replica; pricing consumption, passthrough, flat; connectivity driver; 2 of 5 gates; failing checks: isolation, schema, cost.
Input (facts is 2560 characters; the first 1396 are shown):
{
"task": "blueprint",
"product": "Product: Graphwren\nWhat customers want and why: A product-analytics SaaS; enterprise customers want to point Tableau and Power BI at their own event data instead of exporting CSVs.\nProblem: Customers query their own data (this skill's full scope); data lives in: A cloud warehouse; tenants: 420 (pooled); largest scanned table: 60 GB; scans a day per typical customer: 3\nConsumers: BI tools yes, data engineers no, embedded apps or agents no; arbitrary customer-authored SQL: no; worst-case query threatens the shared cluster: no; clients to support: Tableau, Power BI\nSchema exposed: a curated mart; contract enforcement: None - convention only; mart refresh: not given; engine speaks the Postgres wire protocol: no; connectivity planned: Your own certified JDBC/ODBC driver; credentials: Short-lived tokens (key-pair JWT or OAuth)\nPricing intent: tier; compute paid by: us (provider); customer volume spread: narrow; our cost is consumption-based: no; a compounding asset; hard ship date: no; as-of: 2026-11-01",
"surface": "Views and policies (view | mechanism | identity | probed):\n- shared.events_v | app WHERE tenant_id = ? in the query service (an application-layer WHERE filter) | identity: CURRENT_ROLE() (CURRENT_ROLE or CURRENT_USER) | probed as a consumer: NO\n- shared.sessions_v | secure view joined to the entitlement table (engine policy (secure or dynamic view, row-level security)) | identity: current_account() (a share-aware identity function) | probed as a consumer: yes\n\nCompute handles (handle | timeout | concurrency | spend | who pays):\n- Reader account per customer | timeout yes | concurrency cap yes | spend cap NO | compute paid by us\n- Shared BI warehouse | timeout yes | concurrency cap NO | spend cap yes | compute paid by us\n\nPlanned schema changes (kind | object | how):\n- add | events.utm_campaign | additive",
"carry": "",
"facts": "The page's checks (3 fail, 0 warn, 4 pass, 2 n/a); verdict: no customer gets a handle yet:\n- Problem, architecture and price | problem | pass | Customers query their own data (this skill's full scope)\n- Problem, architecture and price | consumers | na | BI analysts (Tableau, Power BI, Looker) - design for the most demanding: the BI analysts\n- Problem, architecture and price | architecture | na | Zero-copy warehouse share > Per-tenant compute isolation > Replicated copy or read endpoint - a compounding mandate moves the isolation rung up\n- Problem, architecture and price | pricing | pass | Consumption or volume-tiered - you pay the consumers' compute - the passthrough posture arrives with it, and a spend monitor on every account with it; a compounding mandate promotes consumption early - retrofitting a meter under a shipped flat price is a repricing\n- Isolation and schema | isolation | fail | shared.events_v: an application-layer WHERE filter - one missed filter leaks rows, CURRENT_ROLE/CURRENT_USER go NULL across a share, never probed as another tenant\n- Isolation and schema | schema | fail | a curated mart; the contract lives only in a doc - enforce it in CI; 1 planned change, every breaking one staged\n- Connectivity, governance and credentials | connectivity | pass | your own driver: accurate SQLSTATEs, signed per-tool connectors that expire, and first-line support routed\n\n[... 1164 more characters cut from this display ...]"
}
Reply (long strings shortened for display - see the cuts below):
{
"task": "blueprint",
"summary": "A zero-copy warehouse share for Tableau and Power BI, held until shared.events_v becomes a secure view on current_account() probed as another tenant, each reader account gains a spend cap and the Shared BI warehouse a concurrency cap.",
"problem": {
"scope": "Graphwren's enterprise customers query their own event data from Tableau and Power BI instead of exporting CSVs, which is this skill's full scope across 420 pooled tenants.",
"consumers": [
"BI analysts in Tableau: dashboards and extracts over their own events and sessions, about 3 scans a day on a table of up to 60 GB",
"BI analysts in Power BI: the same reviewed dashboard queries, with no customer-authored SQL"
]
},
"architecture": {
"rung": "share",
"promote_when": "Move to per-tenant compute isolation once a tenant's scans a day climb from today's 3 toward dashboard-refresh frequency on the 60 GB events table, once customer-authored SQL or agents are admitted, or once one tenant keeps tripping the [... 26 more characters cut from this display]",
"why": "The event data already sits in a cloud warehouse, a typical customer scans only 3 times a day, the consumers are BI tools with no arbitrary SQL and no query threatens the shared cluster, so the share ranks first, with the compounding [... 37 more characters cut from this display]"
},
"isolation": [
{
"view": "shared.events_v",
"policy": "Rebuild shared.events_v as a secure view joined to the private entitlement table, the same pattern shared.sessions_v already uses, replacing the query-service tenant filter entirely.",
"identity": "current_account()",
"probe": "Query shared.events_v from a second test tenant's reader account and require zero rows belonging to any other tenant, then rerun after every policy change."
},
{
"view": "shared.sessions_v",
"policy": "Keep shared.sessions_v as a secure view joined to the entitlement table, with no direct grant on the underlying sessions table.",
"identity": "current_account()",
"probe": "Keep the passing consumer-side probe and rerun it as another tenant after every policy change, still requiring zero foreign rows."
}
],
"schema": {
"layer": "Serve the curated Graphwren mart behind shared.events_v and shared.sessions_v, never the live operational schema.",
"rule": "The events and sessions tables are additive-only: new columns such as events.utm_campaign go in freely, while no column is renamed, retyped or dropped in place but added, deprecated, migrated and only then removed.",
"ci": "A CI check diffs each mart migration against the published contract and fails the build on any rename, type change or drop, replacing today's convention-only doc."
},
"connectivity": {
"rung": "driver",
"setup": "Graphwren publishes a connection page each for Tableau and Power BI naming the URL template, its certified JDBC/ODBC driver and signed connector with the connector's expiry, TLS as required, and OAuth sign-in."
},
"governance": [
{
"handle": "Reader account per customer",
"timeout": "keep its existing statement timeout on every reader account",
"concurrency": "keep its existing concurrency cap per reader account",
"spend": "add a monthly spend cap on every reader account that suspends queries at the limit, with the amount a proposed starting point to set, not an input figure"
},
{
"handle": "Shared BI warehouse",
"timeout": "keep its existing statement timeout",
"concurrency": "add a per-tenant concurrency cap, proposed starting point 4 concurrent queries per tenant",
"spend": "keep its existing spend cap, and move a tenant that keeps tripping it to its own compute rather than raising it"
}
],
"credentials": {
"order": [
"short-lived tokens (OAuth with PKCE, key-pair JWT)",
"managed secrets",
"static passwords, never issued"
],
"bi": "Tableau and Power BI users sign in with OAuth with PKCE for a token scoped to their own tenant over required TLS, falling back to a key-pair service account only where a tool cannot refresh, with self-service revocation in Graphwren.",
"service": "No service or agent consumers are in scope today; any that arrive authenticate with a tenant-scoped key-pair JWT, never a secret written into a connection string."
},
"pricing": {
"shape": "consumption",
"meter": "event rows queried per month",
"why": "Graphwren pays for every reader account's compute and the compounding mandate promotes the meter early, since retrofitting one under a shipped flat tier would be a repricing, so it is a paid add-on metered on events with a spend monitor on [... 15 more characters cut from this display]"
},
"caveats": [
"No customer gets a handle until shared.events_v passes its probe and every reader account has a spend cap.",
"The mart refresh interval is not given, so freshness cannot be promised yet.",
"The spend and concurrency values are proposed starting points, not figures from Graphwren."
]
}
Cut from this display only (the saved reply and the check use the full text): architecture.promote_when (262 characters, 236 shown); architecture.why (270 characters, 233 shown); pricing.why (253 characters, 238 shown).
What step 5 prints for it: share: A zero-copy warehouse share for Tableau and Power BI, held until shared.events_v becomes a secure view on current_account() probed as another tenant, each reader account gains a spend cap and the Shared BI warehouse a concurrency cap.
The page's check: kept - nothing to flag
contract - An analytics SaaS filtering in app code
The input carries the saved blueprint's decisions (R.laneForm fills carry from the blueprint reply), as the page's handoff does. Breaking changes planned: none; clients: Tableau, Power BI.
Input (facts is 2259 characters; the first 1396 are shown):
{
"task": "contract",
"product": "Product: Graphwren\nWhat customers want and why: A product-analytics SaaS; enterprise customers want to point Tableau and Power BI at their own event data instead of exporting CSVs.\nProblem: Customers query their own data (this skill's full scope); data lives in: A cloud warehouse; tenants: 420 (pooled); largest scanned table: 60 GB; scans a day per typical customer: 3\nConsumers: BI tools yes, data engineers no, embedded apps or agents no; arbitrary customer-authored SQL: no; worst-case query threatens the shared cluster: no; clients to support: Tableau, Power BI\nSchema exposed: a curated mart; contract enforcement: None - convention only; mart refresh: not given; engine speaks the Postgres wire protocol: no; connectivity planned: Your own certified JDBC/ODBC driver; credentials: Short-lived tokens (key-pair JWT or OAuth)\nPricing intent: tier; compute paid by: us (provider); customer volume spread: narrow; our cost is consumption-based: no; a compounding asset; hard ship date: no; as-of: 2026-11-01",
"surface": "Views and policies (view | mechanism | identity | probed):\n- shared.events_v | app WHERE tenant_id = ? in the query service (an application-layer WHERE filter) | identity: CURRENT_ROLE() (CURRENT_ROLE or CURRENT_USER) | probed as a consumer: NO\n- shared.sessions_v | secure view joined to the entitlement table (engine policy (secure or dynamic view, row-level security)) | identity: current_account() (a share-aware identity function) | probed as a consumer: yes\n\nCompute handles (handle | timeout | concurrency | spend | who pays):\n- Reader account per customer | timeout yes | concurrency cap yes | spend cap NO | compute paid by us\n- Shared BI warehouse | timeout yes | concurrency cap NO | spend cap yes | compute paid by us\n\nPlanned schema changes (kind | object | how):\n- add | events.utm_campaign | additive",
"carry": "Architecture: Zero-copy warehouse share - promote when Move to per-tenant compute isolation once a tenant's scans a day climb from today's 3 toward dashboard-refresh frequency on the 60 GB events table, once customer-authored SQL or agents are admitted, or once one tenant keeps tripping the Shared BI warehouse caps.\nSchema: Serve the curated Graphwren mart behind shared.events_v and shared.sessions_v, never the live operational schema.; The events and sessions tables are additive-only: new columns such as events.utm_campaign go in freely, while no column is renamed, retyped or dropped in place but added, deprecated, migrated and only then removed.; CI: A CI check diffs each mart migration against the published contract and fails the build on any rename, type change or drop, replacing today's convention-only doc.\nConnectivity: Your own certified JDBC/ODBC driver - Graphwren publishes a connection page each for Tableau and Power BI naming the URL template, its certified JDBC/ODBC driver and signed connector with the connector's expiry, TLS as required, and OAuth sign-in.\nCredentials: short-lived tokens (OAuth with PKCE, key-pair JWT) > managed secrets > static passwords, never issued; BI: Tableau and Power BI users sign in with OAuth with PKCE for a token scoped to their own tenant over required TLS, falling back to a key-pair service account only where a tool cannot refresh, with self-service revocation in Graphwren.\nPricing: Consumption or volume-tiered - event rows queried per month",
"facts": "The page's checks (3 fail, 0 warn, 4 pass, 2 n/a); verdict: no customer gets a handle yet:\n- Problem, architecture and price | problem | pass | Customers query their own data (this skill's full scope)\n- Problem, architecture and price | consumers | na | BI analysts (Tableau, Power BI, Looker) - design for the most demanding: the BI analysts\n- Problem, architecture and price | architecture | na | Zero-copy warehouse share > Per-tenant compute isolation > Replicated copy or read endpoint - a compounding mandate moves the isolation rung up\n- Problem, architecture and price | pricing | pass | Consumption or volume-tiered - you pay the consumers' compute - the passthrough posture arrives with it, and a spend monitor on every account with it; a compounding mandate promotes consumption early - retrofitting a meter under a shipped flat price is a repricing\n- Isolation and schema | isolation | fail | shared.events_v: an application-layer WHERE filter - one missed filter leaks rows, CURRENT_ROLE/CURRENT_USER go NULL across a share, never probed as another tenant\n- Isolation and schema | schema | fail | a curated mart; the contract lives only in a doc - enforce it in CI; 1 planned change, every breaking one staged\n- Connectivity, governance and credentials | connectivity | pass | your own driver: accurate SQLSTATEs, signed per-tool connectors that expire, and first-line support routed\n\n[... 863 more characters cut from this display ...]"
}
Reply (long strings shortened for display - see the cuts below):
{
"task": "contract",
"summary": "Graphwren's contract for its curated event mart, with connection pages for Tableau and Power BI over its certified JDBC/ODBC driver and OAuth; no customer receives a connection until shared.events_v and the reader-account spend caps pass.",
"contract": {
"schema": "Customers read the curated Graphwren mart through shared.events_v and shared.sessions_v, never the live operational schema. Columns are only ever added, and a CI check against this published contract fails any mart migration that renames, [... 22 more characters cut from this display]",
"quality": "Each view returns only the rows of the reading customer's own account, verified by a probe from another tenant after every policy change. Column names and types match this contract on every mart load.",
"freshness": "The mart refresh interval is not set yet, so Graphwren promises no freshness window until it publishes one here.",
"availability": "Graphwren proposes a monthly availability target for the Tableau and Power BI endpoints, to be published here once agreed; it is a proposed target, not a measured figure."
},
"breaking": [
"a rename of any column or view in shared.events_v or shared.sessions_v",
"a type change of any published column in the events or sessions tables",
"a drop of a column or view that Tableau or Power BI workbooks already read"
],
"changes": [],
"pages": [
{
"client": "Tableau",
"url": "jdbc:graphwren://{host}:{port}/{database}",
"driver": "Graphwren's certified JDBC driver with its signed Tableau connector, renewed before the connector's expiry",
"tls": "TLS required on every connection; unencrypted sessions are refused",
"auth": "OAuth with PKCE issuing a short-lived token scoped to your own tenant, or a key-pair service account where a published workbook cannot refresh"
},
{
"client": "Power BI",
"url": "Server: {host}:{port}; Database: {database}",
"driver": "Graphwren's certified ODBC driver with its signed Power BI connector, renewed before the connector's expiry",
"tls": "TLS required on every connection; unencrypted sessions are refused",
"auth": "OAuth with PKCE issuing a short-lived token scoped to your own tenant, or a key-pair service account where a scheduled refresh cannot sign in"
}
],
"caveats": [
"No customer gets a connection until shared.events_v moves to a secure view on current_account() and passes its probe as another tenant.",
"Every reader account needs a spend cap and the Shared BI warehouse a concurrency cap before launch.",
"The planned events.utm_campaign column is additive, so it ships without staging.",
"Freshness and availability stay open until Graphwren sets a refresh interval and agrees an availability target."
]
}
Cut from this display only (the saved reply and the check use the full text): contract.schema (259 characters, 237 shown).
What step 5 prints for it: Tableau jdbc:graphwren://{host}:{port}/{database} | Power BI Server: {host}:{port}; Database: {database}
The page's check: kept - nothing to flag
blueprint - A billing SaaS exposing its live Postgres
The page read it as: isolation and cost hold - fix the rest before GA; architectures replica, isolated; pricing consumption, flat, passthrough; connectivity postgres; 2 of 5 gates; failing checks: pricing, schema, credentials.
Input (facts is 2353 characters; the first 1399 are shown):
{
"task": "blueprint",
"product": "Product: Billwhistle\nWhat customers want and why: A subscription-billing SaaS; finance teams want psql and Power BI on their invoices and payments without waiting for exports.\nProblem: Customers query their own data (this skill's full scope); data lives in: An OLTP database only; tenants: 1800 (pooled); largest scanned table: 15 GB; scans a day per typical customer: 4\nConsumers: BI tools yes, data engineers yes, embedded apps or agents no; arbitrary customer-authored SQL: no; worst-case query threatens the shared cluster: no; clients to support: Power BI, psql\nSchema exposed: the live operational schema; contract enforcement: None - convention only; mart refresh: every 6 hours; engine speaks the Postgres wire protocol: yes; connectivity planned: A Postgres wire-protocol endpoint; credentials: A static password\nPricing intent: free; compute paid by: the customer; customer volume spread: wide; our cost is consumption-based: no; a compounding asset; hard ship date: no; as-of: 2026-11-01",
"surface": "Views and policies (view | mechanism | identity | probed):\n- invoices | row-level security with FORCE ROW LEVEL SECURITY (engine policy (secure or dynamic view, row-level security)) | identity: tenant set from the verified token in app.tenant_id (tenant set server-side from a verified token) | probed as a consumer: yes\n- payments | row-level security with FORCE ROW LEVEL SECURITY (engine policy (secure or dynamic view, row-level security)) | identity: tenant set from the verified token in app.tenant_id (tenant set server-side from a verified token) | probed as a consumer: yes\n\nCompute handles (handle | timeout | concurrency | spend | who pays):\n- Read replica pool | timeout yes | concurrency cap yes | spend cap yes | compute paid by the customer\n\nPlanned schema changes (kind | object | how):\n- rename | invoices.amount to amount_due | IN PLACE - BREAKING\n- add | payments.settled_at | additive",
"carry": "",
"facts": "The page's checks (3 fail, 0 warn, 4 pass, 2 n/a); verdict: isolation and cost hold - fix the rest before GA:\n- Problem, architecture and price | problem | pass | Customers query their own data (this skill's full scope)\n- Problem, architecture and price | consumers | na | BI analysts (Tableau, Power BI, Looker); data engineers (psql, dbt, notebooks) - design for the most demanding: the BI analysts\n- Problem, architecture and price | architecture | na | Replicated copy or read endpoint > Per-tenant compute isolation - an OLTP-only stack has no warehouse to share from - the zero-copy rung is deleted, not demoted\n- Problem, architecture and price | pricing | fail | a free SQL feature is expensive to over-serve and gives up the price as a governance control\n- Isolation and schema | isolation | pass | 2 views engine-enforced and probed with zero foreign rows\n- Isolation and schema | schema | fail | the live operational schema is exposed - every internal refactor breaks a customer dashboard; the contract lives only in a doc - enforce it in CI; rename invoices.amount to amount_due in place - expand/contract instead\n- Connectivity, governance and credentials | connectivity | pass | A Postgres wire-protocol endpoint - every BI tool, psql and ORM connects through drivers they already ship\n- Connectivity, governance and credentials | cost | pass | 1 handle capped on time, concurrency and\n\n[... 954 more characters cut from this display ...]"
}
Reply:
{
"task": "blueprint",
"summary": "A read replica pool serving a curated mart over the Postgres wire protocol; isolation and cost caps already hold, but the mart, a CI-enforced contract, token credentials and a metered price must land before general availability.",
"problem": {
"scope": "Billwhistle's finance teams query their own invoices and payments from psql and Power BI instead of waiting for exports, which is this skill's full scope across 1800 pooled tenants.",
"consumers": [
"BI analysts in Power BI: scheduled dashboard refreshes over invoices and payments, about 4 scans a day on a table of up to 15 GB",
"Data engineers in psql: the largest pulls of this product, wide reads over invoices and payments"
]
},
"architecture": {
"rung": "replica",
"promote_when": "Move to per-tenant compute isolation once customer-authored SQL or agents are admitted, once one tenant's queries keep tripping the Read replica pool caps, or once the worst psql pull starts to slow the pool for the other 1800 tenants.",
"why": "Billwhistle's data lives only in an OLTP database, which deletes the share rung, and with no arbitrary SQL and no query threatening the cluster at about 4 scans a day, a read replica ranks ahead of per-tenant isolation."
},
"isolation": [
{
"view": "invoices",
"policy": "Keep row-level security with FORCE ROW LEVEL SECURITY on invoices, carried onto the curated mart table so the table owner is filtered too.",
"identity": "tenant set server-side from the verified token in app.tenant_id",
"probe": "Rerun the passing probe as another tenant against invoices after every policy change or mart rebuild, requiring zero foreign rows."
},
{
"view": "payments",
"policy": "Keep row-level security with FORCE ROW LEVEL SECURITY on payments, carried onto the curated mart table, including the new payments.settled_at column.",
"identity": "tenant set server-side from the verified token in app.tenant_id",
"probe": "Rerun the passing probe as another tenant against payments after every policy change or mart rebuild, requiring zero foreign rows."
}
],
"schema": {
"layer": "Serve a curated invoices and payments mart on the replica, refreshed every 6 hours, in place of the live operational schema so an internal refactor no longer breaks a customer dashboard.",
"rule": "The invoices and payments mart is additive-only: payments.settled_at goes in freely, but invoices.amount is not renamed in place - amount_due is added beside it, amount is deprecated, dashboards migrate, and amount is removed last.",
"ci": "A CI check compares each migration with the published mart contract and fails the build on any rename, type change or drop, which would have stopped the in-place rename of invoices.amount."
},
"connectivity": {
"rung": "postgres",
"setup": "Billwhistle publishes a connection page each for Power BI and psql with a URL template such as postgresql://{host}/{database}?sslmode=verify-full, the stock Postgres driver each already ships, TLS as required, and token-based sign-in."
},
"governance": [
{
"handle": "Read replica pool",
"timeout": "keep its existing statement timeout",
"concurrency": "keep its existing concurrency cap, moving a tenant that keeps tripping it to its own compute rather than raising it for everyone",
"spend": "keep its existing spend cap on the customer-paid compute"
}
],
"credentials": {
"order": [
"short-lived tokens (key-pair JWT, OAuth with PKCE)",
"managed secrets",
"static passwords, retired"
],
"bi": "Power BI signs in with OAuth with PKCE for a token scoped to the customer's tenant over required TLS, and psql users fetch a short-lived tenant token from Billwhistle, with self-service revocation replacing the static password.",
"service": "Service users and data engineers' scheduled jobs move to key-pair JWTs scoped to their tenant, with no credential ever written into a connection string."
},
"pricing": {
"shape": "consumption",
"meter": "invoices and payments queried per month",
"why": "A free feature gives up the price as Billwhistle's governance control, customer volumes span a wide range and the compounding mandate promotes the meter early, so SQL access becomes a paid add-on metered on invoices and payments."
},
"caveats": [
"The in-place rename of invoices.amount to amount_due must not ship; stage it through amount_due beside amount.",
"The stated pricing intent was free; the page ranks consumption first and a free SQL feature is ruled out.",
"The static password must be retired before general availability."
]
}
What step 5 prints for it: replica: A read replica pool serving a curated mart over the Postgres wire protocol; isolation and cost caps already hold, but the mart, a CI-enforced contract, token credentials and a metered price must land before general availability.
The page's check: kept - nothing to flag
Build the input yourself
Load the engine in node (global.window = global; require("./qgkit.js"); require("./qgreply.js")), then:
const K = QueryKit, R = QueryReply;
const f = K.form(Object.assign(K.blank(), { name: "Acme", store: "warehouse", clients: "Tableau, psql",
views: "orders_v | secure view joined to the entitlement table | current_account() | yes",
handles: "BI warehouse | yes | yes | no | provider" }));
// f.arch and f.price (the ranked rungs and shapes, with moves), f.protoRec, f.viewRows, f.handleRows, f.breakingPlanned, f.gate
// K.probeSql(f), K.capsCsv(f) - the isolation probe and the caps table
const b = K.buildInput(f, "blueprint"); // or "contract" (R.laneForm(form, "contract", blueprint) fills carry)
const wire = K.toWire(b.input); // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "blueprint");
// R.check(reply, b) // {status: kept or flagged, notes}
Get a token for the steps below from /tokens.html.
Step 1 — a tiny client helper
One function that sends JSON and unwraps the response. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.
BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="YOUR_TOKEN" # from https://query-gate.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}
import json, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN" # from https://query-gate.skillsafe.ai/tokens.html
def api(method, path, body=None, headers=None):
req = urllib.request.Request(BASE + path, method=method,
data=None if body is None else json.dumps(body).encode(),
headers={"Content-Type": "application/json", **({"Authorization": "Bearer " + TOKEN} if TOKEN else {}), **(headers or {})})
with urllib.request.urlopen(req) as r:
env = json.load(r)
if env.get("error"):
raise RuntimeError(env["error"])
return env["data"]
// query-gate.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "YOUR_TOKEN"; // from https://query-gate.skillsafe.ai/tokens.html
async function api(method, path, body, headers = {}) {
const r = await fetch(BASE + path, { method, headers: { "Content-Type": "application/json",
...(TOKEN ? { Authorization: "Bearer " + TOKEN } : {}), ...headers },
body: body === undefined ? undefined : JSON.stringify(body) });
const env = await r.json();
if (env.error) throw new Error(env.error.code + ": " + env.error.message);
return env.data;
}
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
var token = "YOUR_TOKEN" // from https://query-gate.skillsafe.ai/tokens.html
func api(method, path string, body any, headers map[string]string) (map[string]any, error) {
var buf bytes.Buffer
if body != nil { json.NewEncoder(&buf).Encode(body) }
req, _ := http.NewRequest(method, base+path, &buf)
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) }
res, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
defer res.Body.Close()
var env struct { Data map[string]any; Error map[string]any }
json.NewDecoder(res.Body).Decode(&env)
if env.Error != nil { return nil, fmt.Errorf("%v", env.Error) }
return env.Data, nil
}
// Query Gate.java -- Java 17+, with com.fasterxml.jackson.core:jackson-databind
import com.fasterxml.jackson.databind.*;
import java.net.URI; import java.net.http.*; import java.util.*;
public class Query Gate {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "YOUR_TOKEN"; // from https://query-gate.skillsafe.ai/tokens.html
static final ObjectMapper M = new ObjectMapper();
static final HttpClient C = HttpClient.newHttpClient();
static JsonNode api(String method, String path, Object body, Map<String, String> headers) throws Exception {
var b = HttpRequest.newBuilder(URI.create(BASE + path)).header("Content-Type", "application/json");
if (!token.isEmpty()) b.header("Authorization", "Bearer " + token);
if (headers != null) headers.forEach(b::header);
b.method(method, body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(M.writeValueAsString(body)));
JsonNode env = M.readTree(C.send(b.build(), HttpResponse.BodyHandlers.ofString()).body());
if (env.hasNonNull("error")) throw new RuntimeException(env.get("error").toString());
return env.get("data");
}
}
require "json"
require "net/http"
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN" # from https://query-gate.skillsafe.ai/tokens.html
def api(method, path, body = nil, headers = {})
uri = URI(BASE + path)
req = Net::HTTPGenericRequest.new(method, !body.nil?, true, uri.request_uri,
{ "Content-Type" => "application/json" }.merge(TOKEN.empty? ? {} : { "Authorization" => "Bearer #{TOKEN}" }).merge(headers))
req.body = JSON.generate(body) unless body.nil?
env = JSON.parse(Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }.body)
raise env["error"].to_s if env["error"]
env["data"]
end
<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = "YOUR_TOKEN"; // from https://query-gate.skillsafe.ai/tokens.html
function api(string $method, string $path, $body = null, array $headers = []) {
global $TOKEN;
$h = ["Content-Type: application/json"];
if ($TOKEN) $h[] = "Authorization: Bearer $TOKEN";
foreach ($headers as $k => $v) $h[] = "$k: $v";
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $h, CURLOPT_RETURNTRANSFER => true]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$env = json_decode(curl_exec($ch), true);
if (!empty($env["error"])) throw new Exception(json_encode($env["error"]));
return $env["data"];
}
// Program.cs -- .NET 6+ console app
using System.Net.Http.Json; 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 = "YOUR_TOKEN"; // from https://query-gate.skillsafe.ai/tokens.html
static readonly HttpClient C = new();
public static async Task<JsonNode?> Call(string method, string path, object? body = null, Dictionary<string, string>? headers = null) {
var req = new HttpRequestMessage(new HttpMethod(method), Base + path);
if (Token != "") req.Headers.Add("Authorization", "Bearer " + Token);
if (headers != null) foreach (var h in headers) req.Headers.Add(h.Key, h.Value);
if (body != null) req.Content = JsonContent.Create(body);
var env = JsonNode.Parse(await (await C.SendAsync(req)).Content.ReadAsStringAsync())!;
if (env["error"] is JsonNode e && e.GetValueKind() != JsonValueKind.Null) throw new Exception(e.ToJsonString());
return env["data"];
}
}
Step 2 — a token
A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"query-gate"}' | jq -r '.data.token')
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = ""
TOKEN = api("POST", "/guest", {"slug": "query-gate"})["token"]
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = "";
TOKEN = (await api("POST", "/guest", { slug: "query-gate" })).token;
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = ""
g, err := api("POST", "/guest", map[string]string{"slug": "query-gate"}, nil)
if err != nil { panic(err) }
token = g["token"].(string)
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = "";
token = api("POST", "/guest", Map.of("slug", "query-gate"), null).get("token").asText();
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN.replace("")
TOKEN.replace(api("POST", "/guest", { slug: "query-gate" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "query-gate"])["token"];
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
Api.Token = "";
Api.Token = (await Api.Call("POST", "/guest", new { slug = "query-gate" }))!["token"]!.GetValue<string>();
Step 3 — GET /me
Who the token is (subject_type: user or guest) and the credit balance.
api GET /me | jq '.data | {subject_type, credits}'
me = api("GET", "/me")
print(me["subject_type"], me.get("credits"))
const me = await api("GET", "/me");
console.log(me.subject_type, me.credits);
me, _ := api("GET", "/me", nil, nil)
fmt.Println(me["subject_type"], me["credits"])
JsonNode me = api("GET", "/me", null, null);
System.out.println(me.get("subject_type") + " " + me.get("credits"));
me = api("GET", "/me")
puts [me["subject_type"], me["credits"]].inspect
$me = api("GET", "/me");
echo $me["subject_type"], " ", $me["credits"] ?? "", "\n";
var me = await Api.Call("GET", "/me");
Console.WriteLine($"{me!["subject_type"]} {me["credits"]}");
Step 4 — POST /estimate
Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.
# input.json: the run input object - {"task": "blueprint", "product": "Product: Graphwren
What customers want and why: ...", "surface": "Views and policies (view | mechanism | identity | probed):
- shared.events_v | ...", "carry": "", "facts": "The page's checks (...); verdict: no customer gets a handle yet:
- Problem, architecture and price | problem | pass | ..."}
# - see the input table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}'
inp = json.load(open("input.json")) # {"task": "blueprint", "product": "Product: Graphwren
What customers want and why: ...", "surface": "Views and policies (view | mechanism | identity | probed):
- shared.events_v | ...", "carry": "", "facts": "The page's checks (...); verdict: no customer gets a handle yet:
- Problem, architecture and price | problem | pass | ..."}
# - see the input table
est = api("POST", "/estimate", inp)
print(est["hold_credits"], est["min_credits"], est["model"], est.get("warnings"))
import { readFileSync } from "node:fs";
const input = JSON.parse(readFileSync("input.json", "utf8")); // {task: "blueprint" or "contract", product, surface, carry, facts} (see the input table)
const est = await api("POST", "/estimate", input);
console.log(est.hold_credits, est.min_credits, est.model, est.warnings);
raw, _ := os.ReadFile("input.json") // add "os" to the imports
var input map[string]any
json.Unmarshal(raw, &input)
est, _ := api("POST", "/estimate", input, nil)
fmt.Println(est["hold_credits"], est["min_credits"], est["model"])
Map<String, Object> input = M.readValue(new java.io.File("input.json"), Map.class);
JsonNode est = api("POST", "/estimate", input, null);
System.out.println(est.get("hold_credits") + " " + est.get("min_credits") + " " + est.get("model"));
input = JSON.parse(File.read("input.json"))
est = api("POST", "/estimate", input)
puts [est["hold_credits"], est["min_credits"], est["model"]].inspect
$input = json_decode(file_get_contents("input.json"), true);
$est = api("POST", "/estimate", $input);
echo $est["hold_credits"], " ", $est["min_credits"], " ", $est["model"], "\n";
var input = JsonNode.Parse(File.ReadAllText("input.json"))!;
var est = await Api.Call("POST", "/estimate", input);
Console.WriteLine($"{est!["hold_credits"]} {est["min_credits"]} {est["model"]}");
Step 5 — POST /run, then poll the job
Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses query-gate:<task>:<inputHash>:a<attempt> - the lane is part of the key, retry_note is left out of the hash - and its one automatic reformat retry is a new attempt number, named in the page as one extra run. A blueprint reply carries summary, problem, architecture, isolation, schema, connectivity, governance, credentials, pricing and caveats; a contract reply carries summary, contract, breaking, changes, pages and caveats. The code below prints the gist of either: the architecture rung and the summary, or the connection URL template for each client.
KEY="query-gate:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}'
jq -r 'if .task == "contract" then (.pages[] | "\(.client): \(.url)")
else "\(.architecture.rung): \(.summary)" end' reply.json
import hashlib, time
key = "query-gate:" + inp["task"] + ":" + hashlib.sha256(json.dumps(inp).encode()).hexdigest()[:16] + ":a1"
job = api("POST", "/run", inp, {"Idempotency-Key": key})
while True:
j = api("GET", "/jobs/" + job["job_id"])
if j["status"] in ("succeeded", "failed", "cancelled"):
break
time.sleep(2)
reply = json.loads(j["output"]["output"]) # the reply as one JSON object
print(j["charged_credits"], j.get("truncated"), reply["task"])
if reply["task"] == "contract":
for x in reply["pages"]:
print(x["client"], x["url"])
else:
print(reply["architecture"]["rung"], reply["summary"])
import { createHash } from "node:crypto";
const key = `query-gate:${input.task}:${createHash("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 16)}:a1`;
const job = await api("POST", "/run", input, { "Idempotency-Key": key });
let j;
do { await new Promise((r) => setTimeout(r, 2000)); j = await api("GET", "/jobs/" + job.job_id); }
while (!["succeeded", "failed", "cancelled"].includes(j.status));
const reply = JSON.parse(j.output.output);
console.log(j.charged_credits, j.truncated, reply.task);
if (reply.task === "contract") { for (const x of reply.pages) console.log(x.client, x.url); }
else { console.log(reply.architecture.rung, reply.summary); }
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "query-gate:" + input["task"].(string) + ":" + hex.EncodeToString(sum[:8]) + ":a1"
job, _ := api("POST", "/run", input, map[string]string{"Idempotency-Key": key})
var j map[string]any
for {
time.Sleep(2 * time.Second)
j, _ = api("GET", "/jobs/"+job["job_id"].(string), nil, nil)
if s := j["status"]; s == "succeeded" || s == "failed" || s == "cancelled" { break }
}
var reply map[string]any
json.Unmarshal([]byte(j["output"].(map[string]any)["output"].(string)), &reply)
fmt.Println(j["charged_credits"], j["truncated"], reply["task"])
if reply["task"] == "contract" {
ls, _ := reply["pages"].([]any)
for _, x := range ls {
xx := x.(map[string]any)
fmt.Println(xx["client"], xx["url"])
}
} else {
ar, _ := reply["architecture"].(map[string]any)
fmt.Println(ar["rung"], reply["summary"])
}
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "query-gate:" + input.get("task") + ":" + HexFormat.of().formatHex(digest, 0, 8) + ":a1";
JsonNode job = api("POST", "/run", input, Map.of("Idempotency-Key", key)), j;
do { Thread.sleep(2000); j = api("GET", "/jobs/" + job.get("job_id").asText(), null, null); }
while (!Set.of("succeeded", "failed", "cancelled").contains(j.get("status").asText()));
JsonNode reply = M.readTree(j.get("output").get("output").asText());
System.out.println(j.get("charged_credits") + " " + reply.path("task").asText());
if (reply.path("task").asText().equals("contract")) {
for (JsonNode x : reply.path("pages")) System.out.println(x.path("client").asText() + " " + x.path("url").asText());
} else {
System.out.println(reply.path("architecture").path("rung").asText() + " " + reply.path("summary").asText());
}
require "digest"
key = "query-gate:#{input["task"]}:#{Digest::SHA256.hexdigest(JSON.generate(input))[0, 16]}:a1"
job = api("POST", "/run", input, { "Idempotency-Key" => key })
j = nil
loop do
sleep 2
j = api("GET", "/jobs/#{job["job_id"]}")
break if %w[succeeded failed cancelled].include?(j["status"])
end
reply = JSON.parse(j["output"]["output"])
puts [j["charged_credits"], j["truncated"], reply["task"]].inspect
if reply["task"] == "contract"
reply["pages"].each { |x| puts "#{x["client"]} #{x["url"]}" }
else
puts "#{reply["architecture"]["rung"]} #{reply["summary"]}"
end
$key = "query-gate:" . $input["task"] . ":" . substr(hash("sha256", json_encode($input)), 0, 16) . ":a1";
$job = api("POST", "/run", $input, ["Idempotency-Key" => $key]);
do { sleep(2); $j = api("GET", "/jobs/" . $job["job_id"]); }
while (!in_array($j["status"], ["succeeded", "failed", "cancelled"]));
$reply = json_decode($j["output"]["output"], true);
echo $j["charged_credits"], " ", $reply["task"], "\n";
if ($reply["task"] === "contract") { foreach ($reply["pages"] as $x) echo $x["client"], " ", $x["url"], "\n"; }
else { echo $reply["architecture"]["rung"], " ", $reply["summary"], "\n"; }
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"query-gate:{input["task"]}:{Convert.ToHexString(digest, 0, 8).ToLowerInvariant()}:a1";
var job = await Api.Call("POST", "/run", input, new() { ["Idempotency-Key"] = key });
JsonNode j;
do { await Task.Delay(2000); j = (await Api.Call("GET", "/jobs/" + job!["job_id"]))!; }
while (!new[] { "succeeded", "failed", "cancelled" }.Contains(j["status"]!.GetValue<string>()));
var reply = JsonNode.Parse(j["output"]!["output"]!.GetValue<string>())!;
var task = (string?)reply["task"];
Console.WriteLine($"{j["charged_credits"]} {task}");
if (task == "contract") { foreach (var x in reply["pages"]!.AsArray()) Console.WriteLine($"{x!["client"]} {x["url"]}"); }
else { Console.WriteLine($"{reply["architecture"]!["rung"]} {reply["summary"]}"); }
Step 6 — POST /run-stream
The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.
# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: query-gate:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json
rev = json.load(open("input.json")) # the same run input as input.json: {"task": "blueprint", "product": "Product: Graphwren
What customers want and why: ...", "surface": "Views and policies (view | mechanism | identity | probed):
- shared.events_v | ...", "carry": "", "facts": "The page's checks (...); verdict: no customer gets a handle yet:
- Problem, architecture and price | problem | pass | ..."}
# - see the input table
req = urllib.request.Request(BASE + "/run-stream", method="POST", data=json.dumps(rev).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN,
"Idempotency-Key": "query-gate:" + rev["task"] + ":" + hashlib.sha256(json.dumps(rev).encode()).hexdigest()[:16] + ":a1"})
with urllib.request.urlopen(req) as r:
for line in r:
line = line.decode().strip()
if line.startswith("data:"):
print(line[5:].strip()[:200])
const rev = JSON.parse(readFileSync("input.json", "utf8"));
const r = await fetch(BASE + "/run-stream", { method: "POST", body: JSON.stringify(rev), headers: {
"Content-Type": "application/json", Authorization: "Bearer " + TOKEN,
"Idempotency-Key": `query-gate:${rev.task}:${createHash("sha256").update(JSON.stringify(rev)).digest("hex").slice(0, 16)}:a1` } });
const dec = new TextDecoder();
for await (const chunk of r.body) for (const line of dec.decode(chunk).split("\n"))
if (line.startsWith("data:")) console.log(line.slice(5).trim().slice(0, 200));
rraw, _ := os.ReadFile("input.json") // add "bufio", "strings" to the imports
rs := sha256.Sum256(rraw)
req, _ := http.NewRequest("POST", base+"/run-stream", bytes.NewReader(rraw))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Idempotency-Key", "query-gate:"+input["task"].(string)+":"+hex.EncodeToString(rs[:8])+":a1")
res, _ := http.DefaultClient.Do(req)
sc := bufio.NewScanner(res.Body)
for sc.Scan() { if l := sc.Text(); strings.HasPrefix(l, "data:") { fmt.Println(l[5:]) } }
String rev = java.nio.file.Files.readString(java.nio.file.Path.of("input.json"));
var rd = java.security.MessageDigest.getInstance("SHA-256").digest(rev.getBytes());
var sreq = HttpRequest.newBuilder(URI.create(BASE + "/run-stream")).header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token).header("Idempotency-Key", "query-gate:" + input.get("task") + ":" + HexFormat.of().formatHex(rd, 0, 8) + ":a1")
.POST(HttpRequest.BodyPublishers.ofString(rev)).build();
C.send(sreq, HttpResponse.BodyHandlers.ofLines()).body().filter(l -> l.startsWith("data:")).forEach(System.out::println);
rev = File.read("input.json")
uri = URI(BASE + "/run-stream")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Authorization" => "Bearer #{TOKEN}",
"Idempotency-Key" => "query-gate:#{JSON.parse(rev)["task"]}:#{Digest::SHA256.hexdigest(rev)[0, 16]}:a1")
req.body = rev
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |h|
h.request(req) { |res| res.read_body { |chunk| chunk.each_line { |l| puts l if l.start_with?("data:") } } }
end
$rev = file_get_contents("input.json");
$ch = curl_init(BASE . "/run-stream");
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $rev, CURLOPT_HTTPHEADER => [
"Content-Type: application/json", "Authorization: Bearer $TOKEN",
"Idempotency-Key: query-gate:" . json_decode($rev, true)["task"] . ":" . substr(hash("sha256", $rev), 0, 16) . ":a1"],
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { foreach (explode("\n", $chunk) as $l) if (str_starts_with($l, "data:")) echo $l, "\n"; return strlen($chunk); }]);
curl_exec($ch);
var rev = File.ReadAllText("input.json");
var rd = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(rev));
var sreq = new HttpRequestMessage(HttpMethod.Post, Api.Base + "/run-stream") { Content = new StringContent(rev, System.Text.Encoding.UTF8, "application/json") };
sreq.Headers.Add("Authorization", "Bearer " + Api.Token);
sreq.Headers.Add("Idempotency-Key", $"query-gate:{input["task"]}:{Convert.ToHexString(rd, 0, 8).ToLowerInvariant()}:a1");
using var http = new HttpClient();
using var res = await http.SendAsync(sreq, HttpCompletionOption.ResponseHeadersRead);
using var rdr = new StreamReader(await res.Content.ReadAsStreamAsync());
for (string? l; (l = await rdr.ReadLineAsync()) != null;) if (l.StartsWith("data:")) Console.WriteLine(l);