Files
settled-reach/governance
jpmschweitzerandClaude Opus 5 49fa6ada95 feat(config): T-1249 — the contract is a decorator, and now a test
Every non-zero exit names the command that would fix it, and still exits
non-zero. Both halves matter; the second is the one that gets lost, because a
tool that explains itself beautifully and exits 0 looks MORE correct while
having silently disabled its own gate.

core/errors.py holds ReachError(message, fix=) and @handle_errors.
core/logging.py holds @logged, emitting through console rather than a second
sink — one output path, so there is nothing to drift. core/command.py composes
them, and the order is load-bearing: handle_errors wraps logged, so the logger
sees the original exception. Inverted, every failure would be recorded as
"SystemExit" and the log would say nothing about what went wrong while looking
like it worked.

core/ raises SystemExit, not typer.Exit. A service must be callable from a
test, another service, or a future second front end, and an exception type that
only makes sense inside a CLI leaks the transport into every layer.

The check router is retrofitted off its hand-rolled verdict-and-exit pattern —
exactly the boilerplate this removes — and test_check_parity.py passes
unchanged across the retrofit. That test predates the decorators and pins exit
codes against the old script, so it is independent evidence, not a test tuned
to match new behaviour.

Unknown domains and unknown verbs now enumerate what exists instead of only
saying no. That needed a shared group class, which collided with "no typer
outside main.py and router.py" — resolved by sharpening the invariant rather
than breaking it, since its purpose is that a SERVICE never knows it was called
from a CLI. Transport now lives in main.py, router.py and core/cli.py; never in
service.py, schemas.py or helpers.py. The upside is that cli.domain() carries
the settings that were previously per-router decisions, including the
load-bearing rich_markup_mode=None that one forgetful domain could have undone.

test_conformance.py makes five invariants executable, AST-based rather than
grep. Scoped to the package, not the 123 legacy scripts — and deliberately so:
as T-1250 moves each script into domains/, it lands inside the scope and the
rules start applying automatically, so the test's reach grows with the
migration.

Proven to fail before being trusted: removing @command and removing a fix= each
produced a failure naming the file, the line and the reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 15:03:34 +02:00
..

Governance — Decisions, Questions, Rejected

Structured planning records for The Settled Reach. pql decisions sync parses these into .pql/pql.db; query them with pql decisions ….

Layout

Records live in three per-type subdirectories, split by domain:

governance/
  decisions/<domain>.md   # D-NNN — confirmed design decisions
  questions/<domain>.md    # Q-NNN — open questions (may resolve into a D or R)
  rejected/<domain>.md     # R-NNN — rejected alternatives (kept for the audit trail)

The parser infers record type from the parent subdirectory and domain from the filename stem. A ### D-NNN: Title (or Q-/R-) heading begins each record; - **Field:** value lines and inline [D-NNN](…#anchor) links carry the metadata and cross-references pql indexes.

Current domains: architecture, content, economics, perception, process, scope. Create a new <domain>.md in the relevant subdir when records land in a new domain.

Domain guide

When in doubt where a record belongs:

  • architecture — constrains how we build (engine, protocols, data structures, performance).
  • scope — defines what we build (game concept, feature scope, prototype shape).
  • perception — defines what the player observes or knows (camera, fog, LOS, audio).
  • content — defines narrative, NPCs, dialogue, setting, templates.
  • economics — the economics layer (currencies, commodities, corporations, simulation).
  • process — defines how the team works (workflow, commits, branches, reviews).

Cross-domain records live in one file with [D-NNN](../<subdir>/<domain>.md#…) links to the related domain.

Querying

pql decisions list                              # every record
pql decisions list --type confirmed --domain architecture
pql decisions show D-010 --with-tickets         # a record + its implementing tickets
pql decisions read D-238                         # full markdown body
pql decisions refs D-010                          # cross-references in/out
pql decisions coverage                            # decisions ↔ ticket coverage

Adding a record

  1. Claim an ID (no side effects): pql decisions claim D <domain> "title" (use Q for a question, R for a rejected alternative).
  2. Edit the appropriate file (decisions/<domain>.md, questions/<domain>.md, or rejected/<domain>.md). Follow the existing ### D-NNN: Title format.
  3. Commit. The pre-commit hook runs pql decisions validate (malformed-record gate) and stages the planning changelog.
  4. Update relevant agent briefings if needed.

When a question resolves, set its - **Status:** Resolved → [D-NNN](../decisions/<domain>.md#…) line in place — keep the Q-record for the audit trail rather than deleting it.

Maintained by Qatux.

Decisions

Open questions

Resolved questions

Rejected