Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions bin/cross_view_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,10 @@ def _check_cross_view_references(
# Multiple view-types are allowed because a §11 Human-User View may bind to
# both help-text and error-text catalog entries.
_VIEW_TO_CATALOG_TYPES: dict[str, set[str]] = {
"9": {"help-text"}, # placeholder until v1.1 ships input-shape exemplars
"10": {"help-text"}, # placeholder until v1.1 ships output-shape exemplars
"9": {"input-shape"},
"10": {"output-shape"},
"11": {"help-text", "error-text"},
"12": {"api-shape"},
"12": {"api-shape", "ipc-rpc"},
"13": {"log-format", "observability"},
}

Expand Down
11 changes: 11 additions & 0 deletions docs/exemplars/input-shape/axes.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
taxonomy-version: 1
axes:
arg-style:
values: [posix-flags, gnu-long-flags, subcommand-verb, positional-only, json-body, query-string]
description: How inputs are structurally presented to the program. Posix-flags = short-form (-x); gnu-long-flags = long-form (--flag, --flag=value); subcommand-verb = first positional arg is an operation name; positional-only = unnamed positional args; json-body = structured JSON payload in HTTP request body; query-string = URL-encoded key=value pairs in the query component.
optionality:
values: [all-required, all-optional, mixed, schema-validated]
description: Whether inputs are required or optional. All-required = every declared input must be supplied; all-optional = all inputs have defaults; mixed = some required some optional; schema-validated = a schema object (JSON Schema or OpenAPI) declares required/optional per-field.
separator-support:
values: [none, double-dash, content-type-boundary, newline-delimited]
description: How the input boundary between option parsing and positional data is signaled. None = no explicit separator; double-dash = POSIX -- convention ends option scanning; content-type-boundary = MIME multipart boundary header; newline-delimited = each record is a newline-terminated JSON line.
27 changes: 27 additions & 0 deletions docs/exemplars/input-shape/cli-argparse-shape.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
view-types: [input-shape]
conventions:
- "A POSIX utility name consists of lowercase letters and digits; hyphens may appear between name components but not as the first or last character (POSIX.1-2017 §12.1)"
- "Short options are a single hyphen followed by a single alphanumeric character (e.g. `-x`); they may be grouped without whitespace when they take no argument (e.g. `-abc` equals `-a -b -c`)"
- "An option-argument may be supplied directly adjacent to its option letter with no whitespace (`-ofile`) or separated by a single space (`-o file`); both forms must be treated identically"
- "GNU long options use a double-hyphen prefix followed by a word or hyphenated words (e.g. `--output`, `--output-file`); the argument may be supplied as `--option=value` or as two space-separated tokens `--option value`"
- "The double-hyphen token `--` used alone terminates option scanning; all subsequent tokens are treated as non-option arguments even if they begin with `-` (POSIX.1-2017 §12.2 guideline 10)"
- "Options that accept optional arguments (GNU extension) must use the `--option=value` form; `--option value` is not parsed as an optional argument — the value would be treated as the next positional argument"
- "Option processing order: short options in the order given, then long options in the order given; interleaving short and long is permitted unless the implementation explicitly disables it"
- "Operands (non-option arguments) follow all options; POSIX requires operands to appear after all options unless the implementation supports mixing (signaled by `POSIXLY_CORRECT` absence in GNU implementations)"
axes: {arg-style: gnu-long-flags, optionality: mixed, separator-support: double-dash}
calibrated-for: [programmatic-trusted, programmatic-untrusted, human-typed]
taxonomy-version: 1
source-url: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html
last-reviewed: 2026-05-15
---

# cli-argparse-shape input-shape conventions

POSIX defines the canonical shape for command-line utility arguments in §12 of the Base Definitions. The foundational rule is that options begin with a single hyphen and a single character; operands are everything that is not an option or an option-argument. This vocabulary — option, option-argument, operand — is precise: a reviewer can label every token in a command line as exactly one of the three before considering what a token means semantically.

Short-option grouping (`-abc`) is permitted for options that take no argument. The moment one option in the group takes an argument, the remainder of the group characters are that argument — e.g. `-obfile` means option `-o` with argument `bfile`, not three options followed by `file`. Implementing parsers must consume options greedily from left to right within a group.

GNU long options (`--word`) extend POSIX by naming options legibly. GNU's canonical reference is the Coding Standards chapter "Standards for Command Line Interfaces" which adds the `--option=value` form and the `--` terminator. The separator rule is critical: `--option value` assigns `value` only to a required argument; for optional arguments, the `=` form is the only unambiguous parse. A parser that accepts `--option value` for optional arguments silently changes behavior when `value` happens to match the next operand.

The `--` separator is the only POSIX-standardized way to pass operands whose text begins with `-`. Every conformant implementation must treat the token sequence `-- -file` as a single operand whose value is the string `-file`. Omitting `--` support creates an exploitable surface when operand content comes from user-supplied data (filenames, search terms, etc.).
24 changes: 24 additions & 0 deletions docs/exemplars/input-shape/openapi-request-body.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
view-types: [input-shape]
conventions:
- "A Request Body Object in OpenAPI 3.1 has a required `content` field whose keys are media type strings (e.g. `application/json`); the absence of `content` is a schema violation"
- "Each media type entry contains a `schema` field whose value is a Schema Object or Reference Object conformant to JSON Schema draft 2020-12 (OpenAPI 3.1 aligns with JSON Schema 2020-12, not draft-07)"
- "The `required` boolean field on the Request Body Object defaults to `false` when absent; an omitted body on a required=true operation is a 400-level error"
- "Sending `Content-Type: application/json` binds the request body to the schema under that media type key; other content types (e.g. `application/x-www-form-urlencoded`) bind to their respective schema entries"
- "A `$ref` in a Schema Object is resolved against the document's `#/components/schemas` namespace or an external URI; inline schema and referenced schema are semantically equivalent after resolution"
- "The `nullable` keyword is not valid in OpenAPI 3.1 (it was a 3.0 extension); use `type: [string, null]` or `oneOf` with a null type entry to express an optional-valued field"
- "Multiple content types on one operation are valid; the server selects the binding by matching the `Content-Type` header to the `content` map keys using media-type pattern matching (RFC 7231 §3.1.1.5)"
axes: {arg-style: json-body, optionality: schema-validated, separator-support: content-type-boundary}
calibrated-for: [programmatic-trusted, programmatic-untrusted, api-consumer, library-consumer]
taxonomy-version: 1
source-url: https://spec.openapis.org/oas/v3.1.0#request-body-object
last-reviewed: 2026-05-15
---

# openapi-request-body input-shape conventions

OpenAPI 3.1 describes the request body via the Request Body Object, which differs from path and query parameters in one important structural respect: the body's schema is not a single schema but a map from content type to schema. A single operation can declare `application/json` and `application/x-www-form-urlencoded` bodies with distinct schemas — the consumer's `Content-Type` header determines which schema applies. A reviewer checking an operation must match the schema to the incoming content type before validating the payload; applying the JSON schema to a form-encoded body (or vice versa) produces meaningless results.

The alignment with JSON Schema 2020-12 (replacing the 3.0/draft-07 alignment) has one frequently-mishandled consequence: the `nullable` keyword is invalid in 3.1 and must not appear in specs targeting that version. The canonical replacement is a type array (`type: [string, null]`) or a `oneOf` with a null branch. Tools that silently accept `nullable: true` in 3.1 documents are applying 3.0 parsing rules.

The `required` field on the Request Body Object governs whether the body itself may be absent — it does not govern individual fields inside the schema (field-level required is expressed inside the Schema Object's `required` array). This distinction matters: `required: false` on the body means the entire body may be omitted; it says nothing about which fields inside a provided body are mandatory.
11 changes: 11 additions & 0 deletions docs/exemplars/ipc-rpc/axes.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
taxonomy-version: 1
axes:
transport:
values: [unix-socket, tcp-socket, stdin-stdout, named-pipe, shared-memory]
description: The OS-level channel used for IPC. Unix-socket = AF_UNIX SOCK_STREAM or SOCK_SEQPACKET; tcp-socket = AF_INET/AF_INET6 loopback; stdin-stdout = file descriptors 0 and 1 of a child process; named-pipe = mkfifo or CreateNamedPipe; shared-memory = mmap or POSIX shm_open.
framing:
values: [newline-delimited, length-prefix, http-upgrade, none]
description: How message boundaries are marked on the stream. Newline-delimited = each JSON object terminated by \n (NDJSON); length-prefix = a fixed-width byte count preceding each payload; http-upgrade = HTTP/1.1 Upgrade handshake then a framing sub-protocol; none = single-message connection (open, write, read, close).
lifecycle:
values: [per-request, persistent-session, child-process-boundary]
description: How the connection lifetime relates to individual calls. Per-request = open a new connection for each call; persistent-session = one long-lived connection carries many calls; child-process-boundary = connection lifetime equals the child process lifetime (stdin closes = child exits).
25 changes: 25 additions & 0 deletions docs/exemplars/ipc-rpc/subprocess-rpc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
view-types: [ipc-rpc]
conventions:
- "The parent process spawns the child with its stdin and stdout connected to pipes; the child reads JSON-RPC request objects from stdin (fd 0) and writes JSON-RPC response objects to stdout (fd 1)"
- "Each JSON-RPC message on stdin and stdout is a single UTF-8 JSON object terminated by a single newline byte; the child must not emit any non-JSON output to stdout (debug output must go to stderr, fd 2)"
- "EOF on stdin signals orderly shutdown: when the parent closes the write end of the stdin pipe, the child's read on stdin returns 0 bytes; the child must flush any buffered output to stdout, then exit with status 0"
- "The child must not read from stdin and write to stdout concurrently in a single-threaded implementation unless the child uses non-blocking I/O or a select/poll loop; a blocking read on stdin while stdout is full causes deadlock if the parent is also blocked writing"
- "SIGTERM sent to the child is a request for graceful shutdown: the child should stop accepting new requests, complete any in-flight request, flush stdout, and exit; SIGKILL is the parent's fallback if the child does not exit within a timeout"
- "The child's exit status is the machine-level shutdown indicator: exit 0 = clean shutdown; exit non-zero = crash or protocol error; the parent must read the exit status via waitpid to avoid leaving zombie processes"
axes: {transport: stdin-stdout, framing: newline-delimited, lifecycle: child-process-boundary}
calibrated-for: [library-consumer, sdk-author, api-consumer]
taxonomy-version: 1
source-url: https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitpid.html
last-reviewed: 2026-05-15
---

# subprocess-rpc ipc-rpc conventions

The stdin/stdout JSON-line protocol treats the child process lifetime as the session lifetime. The parent opens the channel by spawning the child; the parent closes the channel by closing its write end of the stdin pipe; the protocol ends when the child exits. Every JSON-RPC exchange happens inside that lifecycle boundary — there is no connect or reconnect; there is only spawn and terminate.

The newline-terminated framing rule carries an important constraint for the child: stdout must be used exclusively for JSON-RPC responses. Any diagnostic or log output emitted to stdout corrupts the framing stream from the parent's perspective. The child must redirect all non-response output to stderr. Implementations that mix log lines into stdout produce unparseable frames the first time a log statement fires.

The deadlock scenario in single-threaded implementations is a classic producer/consumer problem: if both parent and child are blocked on writes (parent writing to the child's stdin pipe, child writing to the parent's stdout pipe) and neither pipe has buffer space, neither can proceed. The solution is either a threaded or async read-write loop, or use of non-blocking I/O with select/poll on both pipe file descriptors. Single-threaded synchronous implementations that read one frame then write one frame avoid the deadlock only when the parent follows the same alternating pattern — a fragile assumption.

SIGTERM-based graceful shutdown gives the child the opportunity to complete an in-flight request and flush its output buffer before exiting. The parent should wait a bounded interval after sending SIGTERM (typically 5–30 seconds depending on expected request latency) before escalating to SIGKILL. The parent must call waitpid after the child exits to collect its exit status and prevent a zombie process entry in the process table.
25 changes: 25 additions & 0 deletions docs/exemplars/ipc-rpc/unix-socket-rpc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
view-types: [ipc-rpc]
conventions:
- "The server creates an AF_UNIX socket of type SOCK_STREAM and binds it to a filesystem path; the path must be unlinked before bind or bind will return EADDRINUSE"
- "Each JSON-RPC message is terminated by a single newline byte (0x0A); no other framing is added; the receiver reads until it encounters a newline, then parses the accumulated bytes as a single JSON-RPC object"
- "Messages on a UNIX socket connection are ordered and lossless within a single connection; no message-level sequence numbers are needed for ordering, but JSON-RPC id fields are still required for request/response correlation"
- "Connection lifecycle: server calls accept() to obtain a client file descriptor; for each complete newline-delimited frame the server reads, it writes exactly one newline-delimited response frame (for non-notification requests) or nothing (for notifications); EOF on the read side signals client disconnect and the server closes its end"
- "The socket file descriptor is inherited by child processes unless O_CLOEXEC (or SOCK_CLOEXEC on Linux) is set at creation time; the server must set O_CLOEXEC on both the listening socket and each accepted file descriptor to prevent leaking the IPC channel into subprocesses"
- "SIGPIPE is generated when writing to a socket whose read end has been closed; servers must either install SIG_IGN for SIGPIPE or use MSG_NOSIGNAL on send calls, and must handle EPIPE from write/send as a normal disconnect event"
axes: {transport: unix-socket, framing: newline-delimited, lifecycle: persistent-session}
calibrated-for: [library-consumer, api-consumer, sdk-author]
taxonomy-version: 1
source-url: https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html
last-reviewed: 2026-05-15
---

# unix-socket-rpc ipc-rpc conventions

JSON-RPC 2.0 over a UNIX domain socket combines two independently specified protocols. The transport (AF_UNIX SOCK_STREAM) provides a reliable, ordered, full-duplex byte stream between two processes on the same host. The framing (newline-delimited JSON) turns that byte stream into a sequence of discrete messages. Neither layer knows about the other: the socket does not understand JSON, and JSON-RPC does not know it is running over a UNIX socket.

The newline-delimiter convention is simple but fragile if the JSON serializer emits embedded newlines inside string values. A conformant implementation must ensure that no newline character appears inside the JSON text before the message-terminating newline. The canonical approach is to disable pretty-printing (all JSON in one line) — POSIX imposes no constraint on JSON shape; the newline-delimited convention is a stack-level protocol agreement, not a POSIX requirement.

The O_CLOEXEC discipline is operationally important in any server that spawns subprocesses: without it, a forked child inherits the listening socket and all accepted connection file descriptors. The child holds a reference to the socket even after exec, which prevents the OS from delivering EOF to the client when the server closes its copy. Setting O_CLOEXEC at socket creation eliminates the inheritance path entirely without requiring per-fork close calls.

SIGPIPE handling is mandatory for any persistent-session server. A client that exits while the server is mid-write causes SIGPIPE delivery; the default disposition terminates the process. The correct disposition is SIG_IGN (or MSG_NOSIGNAL) so that write returns -1 with EPIPE instead, which the server handles as a clean disconnect and closes the accepted file descriptor.
11 changes: 11 additions & 0 deletions docs/exemplars/output-shape/axes.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
taxonomy-version: 1
axes:
envelope:
values: [http-status-body, json-rpc-2.0, json-lines, plain-text, structured-log, problem-details-rfc7807]
description: The outermost container that wraps every response. Http-status-body = HTTP status code plus a body; json-rpc-2.0 = the JSON-RPC 2.0 response object (jsonrpc, id, result|error); json-lines = one JSON object per newline; plain-text = unstructured UTF-8 stream; structured-log = keyed log records (logfmt, JSON); problem-details-rfc7807 = RFC 7807 problem object.
success-error-split:
values: [http-only, body-field, exclusive-union, status-plus-body]
description: How success and error outcomes are distinguished. Http-only = HTTP status code is the sole indicator; body-field = a field in the body carries success/error; exclusive-union = exactly one of result or error is present (JSON-RPC model); status-plus-body = HTTP status plus a structured error body.
schema-binding:
values: [openapi-3.1, jsonschema-draft7, none, vendor-spec]
description: Which schema vocabulary constrains the response object. Openapi-3.1 = OpenAPI 3.1 response object; jsonschema-draft7 = JSON Schema draft-07; none = no formal schema; vendor-spec = proprietary specification document.
Loading
Loading