{"openapi":"3.1.0","info":{"title":"Multi Language Showcase service contract","version":"1.0.0","description":"One contract, implemented identically by twenty-one applications in six\nlanguages, all running on Laravel Cloud against one managed Postgres.\n\nEvery service below serves this same document at `/openapi.json`. That is\nthe point: the document is not generated per service from its own code, it\nis the shared source of truth all eleven are validated against. The JSON\nSchema in `info.schema.json` is the same contract expressed for the test\nsuites, and `/info` responses are checked against it in every language.\n"},"servers":[{"url":"https://polyglot-dashboard-main-ih62ch.laravel.cloud","description":"dashboard — Laravel 13 (PHP)"},{"url":"https://polyglot-php-main-7xmet0.laravel.cloud","description":"service-php — vanilla PHP, no framework"},{"url":"https://polyglot-ruby-main-rhf0mh.laravel.cloud","description":"service-ruby — Rails 8, API only"},{"url":"https://polyglot-go-main-yziaow.laravel.cloud","description":"service-go — Go, net/http stdlib"},{"url":"https://polyglot-python-main-u9zdzv.laravel.cloud","description":"service-python — FastAPI"},{"url":"https://polyglot-java-main-tbd.laravel.cloud","description":"service-java — Spring Boot (URL assigned on first Cloud deploy; placeholder until then)"},{"url":"https://polyglot-javascript-main-tbd.laravel.cloud","description":"service-javascript — Node.js, stdlib net/http only, no framework (URL assigned on first Cloud deploy; placeholder until then)"},{"url":"https://polyglot-bun-main-tbd.laravel.cloud","description":"service-bun — Bun runtime, Bun.serve(), no framework (URL assigned on first Cloud deploy; placeholder until then)"},{"url":"https://polyglot-elysia-main-tbd.laravel.cloud","description":"service-elysia — Bun runtime + Elysia framework (URL assigned on first Cloud deploy; placeholder until then)"},{"url":"https://polyglot-hono-main-tbd.laravel.cloud","description":"service-hono — Node.js + Hono framework (URL assigned on first Cloud deploy; placeholder until then)"},{"url":"https://polyglot-nuxt-main-tbd.laravel.cloud","description":"service-nuxt — Nuxt (Nitro server routes) (URL assigned on first Cloud deploy; placeholder until then)"}],"security":[{"bearerToken":[]}],"paths":{"/health":{"get":{"summary":"Liveness check","description":"Unauthenticated, and deliberately writes no event row: Cloud's health\nchecks would otherwise dominate the shared timeline. It reports that\nthe process is alive, not that its dependencies are — a service that\ncannot reach Postgres still answers 200 here and reports the failure\nthrough `event_logged` on /info.\n","security":[],"operationId":"health","responses":{"200":{"description":"The process is alive.","content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","const":"ok"}}}}}}}}},"/info":{"get":{"summary":"What this runtime is, and how it is doing","operationId":"info","responses":{"200":{"description":"The shared contract object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Info"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/call/{target}":{"get":{"summary":"Ask a peer service for its /info, and report the round trip","description":"The cross-language hop. The calling service forwards the bearer token,\nwaits at most 2 seconds, and reports what actually happened: a peer\nthat answers non-2xx is a failed hop (502), never a success carrying an\nerror body.\n","operationId":"call","parameters":[{"name":"target","in":"path","required":true,"description":"The peer to call. A service may call itself.","schema":{"type":"string","enum":["dashboard","service-php","service-ruby","service-go","service-python","service-java","service-javascript","service-bun","service-elysia","service-hono","service-nuxt","service-express","service-nestjs","service-symfony","service-flask","service-django","service-rack","service-wsgi","service-java-plain","service-next","service-tanstack","service-rust"]}}],"responses":{"200":{"description":"The peer answered.","content":{"application/json":{"schema":{"type":"object","required":["from","to","round_trip_ms","response"],"properties":{"from":{"$ref":"#/components/schemas/ServiceName"},"to":{"$ref":"#/components/schemas/ServiceName"},"round_trip_ms":{"type":"integer","minimum":0},"response":{"$ref":"#/components/schemas/Info"}}}}}},"400":{"description":"Unknown target.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","examples":["unknown target: service-cobol"]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"502":{"description":"The peer could not be reached, timed out, or answered non-2xx. The\nreal error is reported; a failed hop is never dressed up as a\nsuccessful one.\n","content":{"application/json":{"schema":{"type":"object","required":["from","to","error"],"properties":{"from":{"$ref":"#/components/schemas/ServiceName"},"to":{"$ref":"#/components/schemas/ServiceName"},"error":{"type":"string","examples":["peer returned 500"]}}}}}}}}},"/openapi.json":{"get":{"summary":"This document","description":"Served by every service, unauthenticated, as JSON.","security":[],"operationId":"openapi","responses":{"200":{"description":"The OpenAPI document.","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"components":{"securitySchemes":{"bearerToken":{"type":"http","scheme":"bearer","description":"The shared `SHOWCASE_TOKEN`, identical across all eleven apps."}},"responses":{"Unauthorized":{"description":"Missing or incorrect bearer token.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"}}}}}}},"schemas":{"ServiceName":{"type":"string","enum":["dashboard","service-php","service-ruby","service-go","service-python","service-java","service-javascript","service-bun","service-elysia","service-hono","service-nuxt","service-express","service-nestjs","service-symfony","service-flask","service-django","service-rack","service-wsgi","service-java-plain","service-next","service-tanstack","service-rust"]},"Info":{"type":"object","description":"Identical in shape across all eleven services. A field a runtime\ncannot determine is null — never omitted, never an empty string,\nnever faked.\n","required":["service","language","runtime_version","framework","framework_version","region","instance_id","commit","started_at","uptime_seconds","memory_mb","memory_rss_mb","boot_ms","cpu_count","cpu_count_host","message","event_logged"],"additionalProperties":false,"properties":{"service":{"$ref":"#/components/schemas/ServiceName"},"language":{"type":"string","enum":["PHP","Ruby","Go","Python","Java","JavaScript","Rust"]},"runtime_version":{"type":["string","null"],"examples":["8.5.10","1.26.8","3.12.14","21.0.4","22.22.3","1.3.13"]},"framework":{"type":["string","null"],"description":"Null for the framework-less services (service-php,\nservice-javascript, service-bun), by design — each one has a\nsame-language sibling WITH a framework (dashboard/Laravel,\nservice-hono, service-elysia respectively) so the comparison is\ndeliberate, not an omission.\n","examples":["Laravel","Rails","net/http","FastAPI","Spring Boot","Hono","Elysia","Nuxt",null]},"framework_version":{"type":["string","null"]},"region":{"type":["string","null"],"examples":["eu-west-2"]},"instance_id":{"type":["string","null"]},"commit":{"type":["string","null"]},"started_at":{"type":"string","pattern":"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})$","description":"A pattern rather than `format: date-time` on purpose. Under 2020-12\n`format` is an annotation, not an assertion, so validators disagree\nabout whether to enforce it; `pattern` is enforced by every draft,\nwhich is what a contract shared by eleven implementations needs.\n"},"uptime_seconds":{"type":"integer","minimum":0},"memory_mb":{"type":["number","null"],"minimum":0,"description":"Each runtime's own notion of memory — NOT comparable across\nservices, and that is the point of also reporting\n`memory_rss_mb` below. Go reports heap alloc\n(`runtime.MemStats.Alloc`), Ruby and Python resident set size\n(Ruby already read this the same way `memory_rss_mb` does\neverywhere now; Python reports `getrusage().ru_maxrss`, peak\nRSS rather than current), PHP its per-request memory arena\n(`memory_get_usage(true)`), Java its used JVM heap\n(`totalMemory() - freeMemory()`). The gap between this number\nand `memory_rss_mb` is itself informative: it shows how much of\na runtime's real footprint sits outside the number that runtime\nwould otherwise brag about. Null where it cannot be determined.\n"},"memory_rss_mb":{"type":["number","null"],"minimum":0,"description":"Resident set size in MiB, measured identically in every\nservice by reading `VmRSS` from `/proc/self/status` — the one\ngenuinely comparable memory number in this contract, because\nit is the same measurement taken the same way regardless of\nlanguage or runtime. All eleven services run on Linux containers,\nwhere `/proc` is always present. Null — never a fabricated\nnumber — when it cannot be read.\n"},"boot_ms":{"type":["number","null"],"minimum":0,"description":"Milliseconds from the OS-reported process start to the moment\nthis service became ready to serve requests, captured once at\nstartup and returned unchanged thereafter. Measured the same\nway in every service that has a long-lived process to measure:\nthe process's start time is read from `/proc/self/stat` (field\n22, `starttime`, in clock ticks since boot; Linux fixes USER_HZ\nat 100 for this field on every architecture that matters here,\nso ticks/100 is seconds) and compared against `/proc/uptime` at\nthe ready moment — no wall clocks or NTP skew involved, and no\ndependency on any framework's own notion of \"boot time\".\nResolution is limited to whole clock ticks (~10ms), so very\nfast boots may read as a small multiple of 10 rather than a\nprecise sub-millisecond figure — an honest limit of the\nmeasurement, not a fabricated precision. This is the metric\nexpected to actually differentiate these runtimes (single-digit\nmilliseconds for a compiled Go binary vs. multi-second JVM\nstartup for Spring Boot), which matters on a platform that\nscales to zero.\n\nAlways `null` for `service-php` and `dashboard`. Both run as a\nper-request PHP runtime (`php -S` forking a fresh interpreter\nper request; standard, non-Octane `php artisan serve` resetting\nall in-process state between requests on the same worker) with\nno persistent application process whose start this field could\nhonestly describe. Measuring \"OS process start to ready\" for\neither would actually measure the idle gap between the\ncontainer starting and the first request arriving — an earlier\nversion of this contract did exactly that and reported it as a\n28-second PHP \"boot\", five times slower than the JVM it was\nsitting next to in this same comparison, which is backwards.\nRather than silently approximate, or invent a differently-timed\nnumber in a field whose name and description promise a\nprocess boot, this field is null for both PHP services. Null\nhere means \"this runtime has no process boot to measure\", not\n\"unknown\" — a fact about the runtime, not a measurement gap.\nFor every other runtime, null still means the true process\nstart could not be determined rather than an approximated\nguess.\n"},"cpu_count":{"type":["integer","null"],"minimum":1,"description":"CPU actually available to this container: the cgroup CPU\nquota, converted to a whole number of cores. Read from cgroup\nv2's `/sys/fs/cgroup/cpu.max` (`<quota> <period>` in\nmicroseconds, or the literal `max` for no quota), falling back\nto cgroup v1's `cpu.cfs_quota_us` and `cpu.cfs_period_us` (a\nquota of `-1` meaning no quota) when the v2 file is absent.\nWhen a quota is set, this is `ceil(quota / period)` — e.g. a\n0.25-core quota (`25000 100000`) reports `1`, matching how a\ncontainer with less than one full core to itself still gets\nscheduled in whole-core terms. When there is no quota (or it\ncannot be read), this equals `cpu_count_host` below, since the\nfull host processor count is then genuinely what the container\ncan use. Measured the same way in every service — this field\nused to be each runtime's own standard-library CPU count\n(`runtime.NumCPU()`, `os.cpu_count()`, ...), which is exactly\nthe `memory_mb`-vs-`memory_rss_mb` problem this contract\nalready fixed once: the JVM's `Runtime.availableProcessors()`\nhas been cgroup-aware since JDK 10 and correctly reported the\ncontainer's real quota, while every other runtime reported the\nhost's logical processor count, so Java alone showed `1`\nagainst everyone else's `4` on identical infrastructure — not\nbecause Java's container was smaller, but because only Java was\nbeing honest about it. Null — never guessed — when neither\ncgroup version's files can be read.\n"},"cpu_count_host":{"type":["integer","null"],"minimum":1,"description":"Logical processors visible to the process, read through each\nruntime's standard library (e.g. `runtime.NumCPU()` in Go,\n`os.cpu_count()` in Python, `Runtime.availableProcessors()`\nforced to ignore container support in Java). This is the raw,\nNOT cgroup-adjusted figure `cpu_count` used to report before\nthis contract's CPU fix — kept as its own field because the gap\nbetween it and `cpu_count` is itself informative: on Laravel\nCloud, every container we've measured shares a host with more\nprocessors visible than the container is actually entitled to,\nso this number is expected to read higher than `cpu_count` and\nmust never be mistaken for it. Null where it cannot be\ndetermined.\n"},"message":{"type":"string"},"event_logged":{"type":"boolean","description":"Whether this request's row reached the shared Postgres. False means\nthe service is serving but cannot write — a visible state, not a\nsilent one.\n"}}}}}}