From b93a585be98da1b27c2d1f5c54e4bf06690c1346 Mon Sep 17 00:00:00 2001 From: Takashi Matsuyama Date: Fri, 16 Oct 2026 07:21:08 +0900 Subject: [PATCH 1/2] docs: Reserve - for stdin on the input-file flags, guarantee review record's input shape, and say what a signal's exit code means across platforms compatibility.md now says that a flag coming to accept a value it refuses is additive within 1.x, and fixes the meaning of one such value now: if one of the six input-file flags comes to accept -, it will read standard input, never a file named -. The deprecation policy records refusing - before 1.0, with no warning release, as its one exception to a flag keeping what it accepts through 0.x: the file stays on disk and is read as ./-. basou review record's JSON input joins basou decision capture's as a documented input shape. For a child of exec or run that a signal ended, the five fixed values are spelled out, any other signal is promised 128 plus its number on the platform basou runs on, comparable only there, with the recorded signal names saying which it was, and a signal Node has no name for is stated to be outside the promise: Node reports that child as having exited 0, so basou exits 0 too. terminal-and-import.md points session import's refusal of --from - at the reservation. --- CHANGELOG.md | 13 ++++++++ docs/spec/compatibility.md | 55 +++++++++++++++++++++++++++----- docs/spec/terminal-and-import.md | 6 ++-- 3 files changed, 64 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4203b6e..e1e53c97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,19 @@ All notable changes to **basou** are recorded here. The project follows `--from-file ./-` for a file named `-`. With `-` refused by all six flags, a later release can read stdin for it by adding to what they accept, without changing what any of them means. +- **`docs/spec/compatibility.md` reserves `-` for standard input, guarantees + `basou review record`'s input shape, and says what a signal's exit code + means across platforms.** A flag coming to accept a value it refuses is + additive within `1.x`, and if one of the six input-file flags comes to + accept `-`, it will read standard input. `basou review record`'s JSON input + is now a documented input shape, like `basou decision capture`'s. For a + child of `basou exec` or `basou run` that a signal ended, any signal but the + five with fixed values gives `128` plus its number on the platform basou + runs on, comparable only there, and a signal Node has no name for is outside + the promise, as basou exits `0` for it. The deprecation policy records + refusing `-` before `1.0` as its one exception to a flag keeping what it + accepts through `0.x`. `docs/spec/terminal-and-import.md` points + `session import`'s refusal of `--from -` at the reservation. ### Fixed diff --git a/docs/spec/compatibility.md b/docs/spec/compatibility.md index e313b64f..499abcd1 100644 --- a/docs/spec/compatibility.md +++ b/docs/spec/compatibility.md @@ -17,11 +17,11 @@ At `1.0`, semantic versioning applies to exactly three surfaces: 1. **The `basou` CLI** — the set of commands and subcommands, their flags, exit codes, the documented `--json` output shapes of the commands that offer one, and the documented JSON **input** shapes of the commands that read one - from stdin or `--file` (today: `basou decision capture`). An input shape is a - contract for the same reason an output shape is — something else produces it, - and tightening what is accepted breaks that producer exactly as surely as - dropping a field breaks a consumer. Accepting MORE is additive; accepting - less is not. + from stdin or `--file` (today: `basou decision capture` and `basou review + record`). An input shape is a contract for the same reason an output shape + is — something else produces it, and tightening what is accepted breaks that + producer exactly as surely as dropping a field breaks a consumer. Accepting + MORE is additive; accepting less is not. 2. **The `@basou/sdk` package** — its exported read-only API for reading a workspace's provenance. 3. **The `.basou/` on-disk format** — the durable file schemas (manifest, @@ -37,6 +37,21 @@ below, and the extension rules of [schemas for the on-disk format, state the exceptions to that rule and what bounds each of them. +### A value a flag refuses + +A flag coming to accept a value it refuses today is additive, as accepting more +of an input shape is: every call that worked before works the same, and a call +that was refused gave nothing to build on but its failure. So within `1.x` a +flag may come to accept a value it now refuses. + +One such value has its meaning fixed now, so that accepting it later cannot +surprise anyone: `-` as the file of an input-file flag — `--file` on `basou +decision capture`, `basou review record` and `basou note`, `--from-file` on +`basou task new` and `basou session note`, and `--from` on `basou session +import`. Each of them refuses `-` today. If one comes to accept it, `-` will +mean standard input, never a file named `-`. A file named `-` is read by +naming it `./-`. + ### `--version`: the first token is the contract, the rest is build identity `basou --version` prints the semver version as its **first whitespace-delimited @@ -81,9 +96,19 @@ an error, for instance). - When the child is ended by a signal, basou exits with `128` plus a signal's number: the signal basou itself received while the child ran, when one reached it, and otherwise the signal that ended the child. `basou exec - --timeout` ends the child with `SIGTERM`, then `SIGKILL`. The number is - exact for `SIGHUP`, `SIGINT`, `SIGQUIT`, `SIGKILL` and `SIGTERM`; any other - signal gives a value above `128` that does not identify it. + --timeout` ends the child with `SIGTERM`, then `SIGKILL`. The value is the + same on every platform for `SIGHUP` (`129`), `SIGINT` (`130`), `SIGQUIT` + (`131`), `SIGKILL` (`137`) and `SIGTERM` (`143`). Any other signal gives + `128` plus its number on the platform basou runs on, the value that + platform's shell reports. Those numbers differ between platforms — `138` is + `SIGUSR1` on Linux and `SIGBUS` on macOS — so such a value is comparable + only on one platform; the signal names recorded on the session's + `command_executed` event (`signal`, `received_signal`) say which signal it + was. +- A signal Node has no name for — `SIGEMT` on macOS, the real-time signals on + Linux — reaches basou as if the child had exited `0`, so basou exits `0` + and records the session as completed. It is outside the promise above: + basou cannot tell such a child from one that exited `0`. - When basou itself fails, it exits `1` — including when it fails after the child has exited, so a child that exited `0` is then reported as `1`. A caller cannot tell a basou failure apart from a child that exited `1`, and @@ -525,6 +550,20 @@ requirement therefore lands on the input shape. `record` instead names the vessel it used on its receipt, which needs no new flag on a surface this line treats as frozen. +A flag also keeps what it accepts through `0.x`, with one exception: a value +whose present meaning `1.0` would otherwise freeze in the way of a +[reservation](#a-value-a-flag-refuses) is refused before `1.0`. Whether a +warning release comes first depends on what a caller would lose, since a +warning release exists so that a caller does not lose input it cannot easily +make again. A value that names a file loses nothing: the file stays on disk +and is still read under another name, which the refusal gives. So it is +refused outright. + +> Example: `-` as the file of `--file` on `basou decision capture`, `basou +> review record` and `basou note`, and of `--from` on `basou session import`. +> Each read it as a file named `-` until it was refused, in the release after +> `0.68.4`; the refusal says to name such a file `./-`. + After `1.0`, removing or changing the meaning of any guaranteed-surface element requires a major bump; additions within a line remain backward-compatible. diff --git a/docs/spec/terminal-and-import.md b/docs/spec/terminal-and-import.md index 0ab17327..7c864aba 100644 --- a/docs/spec/terminal-and-import.md +++ b/docs/spec/terminal-and-import.md @@ -83,8 +83,10 @@ basou session import --format json --from ``` - `--format` and `--from` are both required; the JSON is read from the - `--from` file (not from stdin). `--from -` is refused: `-` is reserved for - standard input. A file named `-` is read with `--from ./-`. + `--from` file. This command does not read stdin today, and `--from -` is + refused: `-` is [reserved for standard + input](compatibility.md#a-value-a-flag-refuses). A file named `-` is read + with `--from ./-`. - It is converted to the basou event schema. - Events are appended to `.basou/sessions//events.jsonl`. From 9b09cecbca12edc6fc22a3fb17175b96c54249de Mon Sep 17 00:00:00 2001 From: Takashi Matsuyama Date: Fri, 16 Oct 2026 07:35:38 +0900 Subject: [PATCH 2/2] docs: Bound the reservation, the refused-value rule and the signal promise, and say what review record's shape covers A refused value becomes accepted as an addition only when every invocation accepted before parses and behaves as before; making a value optional does not qualify (--file --json reads a file named --json today). Accepting one is the one way an invocation crosses zero within 1.x. The reservation of - covers an input-file flag added later too. review record's documented shape is the Input format section of its --help: fields, types, required fields and enumerated values. Whether a repos entry names a repository root follows the rule review-gaps binds with. session import's envelope is named among the input shapes, under its own rule. For a signal-ended child, only a SIGINT or SIGTERM sent to basou counts; any other ends basou before the run is recorded. The values are for POSIX platforms and a POSIX shell such as sh, bash or zsh. A signal Node has no name for is described as today's behaviour, not promised, and schemas.md says the same of command_executed. The 0.x exception names - and dates from the policy's adoption in 0.31.0. The README mentions it, and the CHANGELOG entry for the signal fix no longer says only the five values are promised. --- CHANGELOG.md | 37 ++++++++++------ README.md | 5 ++- docs/spec/compatibility.md | 91 ++++++++++++++++++++++---------------- docs/spec/schemas.md | 11 +++++ 4 files changed, 91 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1e53c97..2aa71414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,17 +23,26 @@ All notable changes to **basou** are recorded here. The project follows without changing what any of them means. - **`docs/spec/compatibility.md` reserves `-` for standard input, guarantees `basou review record`'s input shape, and says what a signal's exit code - means across platforms.** A flag coming to accept a value it refuses is - additive within `1.x`, and if one of the six input-file flags comes to - accept `-`, it will read standard input. `basou review record`'s JSON input - is now a documented input shape, like `basou decision capture`'s. For a + means across platforms.** Within `1.x` a flag may come to accept a value it + refuses, as an addition, only when every invocation accepted before parses + and behaves as before. If an input-file flag, one of today's six or one + added later, comes to accept `-`, it will read standard input. `basou review + record`'s JSON input is now a documented input shape, like `basou decision + capture`'s: the fields, types, required fields and enumerated values of the + `Input format` section of `--help`, while whether a `repos` entry names a + repository root follows `basou review-gaps`. The envelope of `basou session + import` is named among the input shapes, under its own version rule. For a child of `basou exec` or `basou run` that a signal ended, any signal but the - five with fixed values gives `128` plus its number on the platform basou - runs on, comparable only there, and a signal Node has no name for is outside - the promise, as basou exits `0` for it. The deprecation policy records - refusing `-` before `1.0` as its one exception to a flag keeping what it - accepts through `0.x`. `docs/spec/terminal-and-import.md` points - `session import`'s refusal of `--from -` at the reservation. + five with fixed values gives `128` plus its number on the POSIX platform + basou runs on, comparable only there. A signal sent to basou itself counts + only if it is `SIGINT` or `SIGTERM`; any other ends basou before the run is + recorded. A signal Node has no name for is outside the promise, and today's + exit `0` for it is described, not promised (`docs/spec/schemas.md` says the + same of the event). The deprecation policy records refusing `-` as the one + exception, since the policy was adopted in `0.31.0`, to a flag keeping what + it accepts through `0.x`, and the README says so. + `docs/spec/terminal-and-import.md` points `session import`'s refusal of + `--from -` at the reservation. ### Fixed @@ -44,10 +53,10 @@ All notable changes to **basou** are recorded here. The project follows number on the platform basou runs on: `SIGSEGV` gives `139` and `SIGPIPE` `141` on Linux and macOS, while `SIGUSR1` gives `138` on Linux and `158` on macOS, so such a value is comparable only on one platform, and the recorded - signal name says which signal it was. Only the five signals' values are - promised. A signal Node has no name for (`SIGEMT` on macOS, the real-time - signals on Linux) still reaches basou as an exit code of `0`, so basou - still exits `0` for it. + signal name says which signal it was. `docs/spec/compatibility.md` now + promises this (see Changed). A signal Node has no name for (`SIGEMT` on + macOS, the real-time signals on Linux) still reaches basou as an exit code + of `0`, so basou still exits `0` for it. ## 0.68.4 — 2026-10-15 diff --git a/README.md b/README.md index 53a8ddad..e062920d 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,10 @@ zero-network design. What that means for you today: - **The `basou` CLI surface is frozen for the `0.x` line** — commands and - flags are stable, except the experimental commands that + flags are stable, except that `-` as the file of an input-file flag is + refused, [reserved for standard + input](docs/spec/compatibility.md#a-value-a-flag-refuses), and except the + experimental commands that [docs/spec/compatibility.md](docs/spec/compatibility.md#what-is-not-guaranteed) lists, which may also write files beside one of yours that they read, never under `.basou/` (`basou board record` writes records beside its diff --git a/docs/spec/compatibility.md b/docs/spec/compatibility.md index 499abcd1..00b4e0b7 100644 --- a/docs/spec/compatibility.md +++ b/docs/spec/compatibility.md @@ -17,11 +17,19 @@ At `1.0`, semantic versioning applies to exactly three surfaces: 1. **The `basou` CLI** — the set of commands and subcommands, their flags, exit codes, the documented `--json` output shapes of the commands that offer one, and the documented JSON **input** shapes of the commands that read one - from stdin or `--file` (today: `basou decision capture` and `basou review - record`). An input shape is a contract for the same reason an output shape - is — something else produces it, and tightening what is accepted breaks that + from stdin or an input-file flag (today: `basou decision capture`, `basou + review record`, and the envelope of `basou session import`, whose version + follows [its own rule](#the-import-envelope-version-may-move-at-a-minor)). + An input shape is a contract for the same reason an output shape is — + something else produces it, and tightening what is accepted breaks that producer exactly as surely as dropping a field breaks a consumer. Accepting - MORE is additive; accepting less is not. + MORE is additive; accepting less is not. For `basou decision capture` and + `basou review record`, the documented shape is the `Input format` section of + the command's `--help`, which is contract rather than presentation: the + fields, their JSON types, which are required, and the values a field + enumerates. Whether a `repos` entry of `basou review record` names a + repository root on the machine is judged by the rule `basou review-gaps` + binds a record with, and may change with that rule. 2. **The `@basou/sdk` package** — its exported read-only API for reading a workspace's provenance. 3. **The `.basou/` on-disk format** — the durable file schemas (manifest, @@ -39,18 +47,24 @@ of them. ### A value a flag refuses -A flag coming to accept a value it refuses today is additive, as accepting more -of an input shape is: every call that worked before works the same, and a call -that was refused gave nothing to build on but its failure. So within `1.x` a -flag may come to accept a value it now refuses. +Within `1.x` a flag may come to accept a value it refuses today, and that is +additive, as accepting more of an input shape is — but only when every +invocation accepted before is parsed and behaves as before. Making an option's +value optional does not qualify: `--file --json` reads a file named `--json` +today, and would parse `--json` as a flag instead. Accepting a refused value +moves the invocations it accepts from a non-zero exit to `0`, the one way an +invocation crosses zero within `1.x`; a [split of the non-zero +values](#exit-codes-zero-is-success-and-a-failures-value-may-be-refined) never does. One such value has its meaning fixed now, so that accepting it later cannot -surprise anyone: `-` as the file of an input-file flag — `--file` on `basou -decision capture`, `basou review record` and `basou note`, `--from-file` on -`basou task new` and `basou session note`, and `--from` on `basou session -import`. Each of them refuses `-` today. If one comes to accept it, `-` will -mean standard input, never a file named `-`. A file named `-` is read by -naming it `./-`. +surprise anyone: `-` as the file of an input-file flag, a flag that names a +file basou reads its input from — `--file` on `basou decision capture`, `basou +review record` and `basou note`, `--from-file` on `basou task new` and `basou +session note`, and `--from` on `basou session import`. Each of them refuses +`-` today, and an input-file flag added later either refuses it too or reads +standard input for it from the start. If one comes to accept it, `-` will mean +standard input, never a file named `-`. A file named `-` is read by naming it +`./-`. ### `--version`: the first token is the contract, the rest is build identity @@ -94,21 +108,25 @@ an error, for instance). - When the child exits with a code, basou exits with that code. - When the child is ended by a signal, basou exits with `128` plus a signal's - number: the signal basou itself received while the child ran, when one - reached it, and otherwise the signal that ended the child. `basou exec - --timeout` ends the child with `SIGTERM`, then `SIGKILL`. The value is the - same on every platform for `SIGHUP` (`129`), `SIGINT` (`130`), `SIGQUIT` - (`131`), `SIGKILL` (`137`) and `SIGTERM` (`143`). Any other signal gives - `128` plus its number on the platform basou runs on, the value that - platform's shell reports. Those numbers differ between platforms — `138` is + number: the `SIGINT` or `SIGTERM` basou itself received while the child ran, + when one reached it, and otherwise the signal that ended the child. Any other + signal sent to basou ends basou as it ends any process, and the run's + `command_executed` event is not written. `basou exec --timeout` ends the + child with `SIGTERM`, then `SIGKILL`. The value is the same on every POSIX + platform for `SIGHUP` (`129`), `SIGINT` (`130`), `SIGQUIT` (`131`), `SIGKILL` + (`137`) and `SIGTERM` (`143`). Any other signal gives `128` plus its number + on the platform basou runs on, the value a POSIX shell such as `sh`, `bash` + or `zsh` reports there. Those numbers differ between platforms — `138` is `SIGUSR1` on Linux and `SIGBUS` on macOS — so such a value is comparable - only on one platform; the signal names recorded on the session's + only on one platform; the signal names recorded on the run's `command_executed` event (`signal`, `received_signal`) say which signal it was. - A signal Node has no name for — `SIGEMT` on macOS, the real-time signals on - Linux — reaches basou as if the child had exited `0`, so basou exits `0` - and records the session as completed. It is outside the promise above: - basou cannot tell such a child from one that exited `0`. + Linux — reaches basou as if the child had exited `0`, and is outside the + promise above. Today basou then exits `0`, records an `exit_code` of `0` + with no `signal`, and marks the session completed. None of that is + promised: a later basou, or a later Node, may report such a signal. A + reader cannot rule one out from an `exit_code` of `0`. - When basou itself fails, it exits `1` — including when it fails after the child has exited, so a child that exited `0` is then reported as `1`. A caller cannot tell a basou failure apart from a child that exited `1`, and @@ -550,19 +568,16 @@ requirement therefore lands on the input shape. `record` instead names the vessel it used on its receipt, which needs no new flag on a surface this line treats as frozen. -A flag also keeps what it accepts through `0.x`, with one exception: a value -whose present meaning `1.0` would otherwise freeze in the way of a -[reservation](#a-value-a-flag-refuses) is refused before `1.0`. Whether a -warning release comes first depends on what a caller would lose, since a -warning release exists so that a caller does not lose input it cannot easily -make again. A value that names a file loses nothing: the file stays on disk -and is still read under another name, which the refusal gives. So it is -refused outright. - -> Example: `-` as the file of `--file` on `basou decision capture`, `basou -> review record` and `basou note`, and of `--from` on `basou session import`. -> Each read it as a file named `-` until it was refused, in the release after -> `0.68.4`; the refusal says to name such a file `./-`. +Since this policy was adopted (`0.31.0`), a flag has also kept what it +accepts, with one exception: `-` as the file of an input-file flag, refused +before `1.0` so that `1.0` does not freeze it as a file name in the way of +the [reservation](#a-value-a-flag-refuses). Four flags read it as a file named +`-` until the release after `0.68.4`: `--file` on `basou decision capture`, +`basou review record` and `basou note`, and `--from` on `basou session +import`. It was refused outright, with no warning release. A warning release +exists so that a caller does not lose input it cannot easily make again; here +the input is a file, which stays on disk and is read as `./-`, as the refusal +says. No other value is refused this way before `1.0`. After `1.0`, removing or changing the meaning of any guaranteed-surface element requires a major bump; additions within a line remain backward-compatible. diff --git a/docs/spec/schemas.md b/docs/spec/schemas.md index 220a6f93..6ae4d9f1 100644 --- a/docs/spec/schemas.md +++ b/docs/spec/schemas.md @@ -401,6 +401,17 @@ Events written by the import paths additionally carry an optional top-level | Review | `review_recorded` | self-reported record that an adversarial / second-opinion review ran. Emitted by `basou review record`. `reviewer` + `target` are required; `repos` (the repository paths reviewed) is what lets `basou review-gaps` bind the record to a unit of work, since the record itself lands in the planning repo. | | Adapter | `adapter_output` | adapter output (summary only; raw kept separately) | +### `command_executed`: a signal Node has no name for + +`exit_code` is the code the child exited with, and `signal` the signal that +ended it; an `exit_code` of `null` means no code was observed — a signal ended +the child, or the source did not record one. One case falls outside that: a +signal Node has no name for — `SIGEMT` on macOS, the real-time signals on +Linux — reaches basou as if the child had exited `0`, so the event holds an +`exit_code` of `0` and no `signal`. An `exit_code` of `0` therefore does not +rule such a signal out. That is how basou records it today, not a promise (see +[compatibility](compatibility.md#exit-codes-zero-is-success-and-a-failures-value-may-be-refined)). + ### `file_changed.change_type` `change_type` is one of `added`, `modified`, `deleted` and `renamed`. It says