M3a: Freeze REST and MCP interface contracts #46

Merged
adrian.tkacz merged 2 commits from m3a-interface-contracts into develop 2026-09-06 23:46:40 +00:00
Member

Closes #45 (via human merge gate; the issue closes when this PR is merged).

What

M3a — REST/MCP planning gate. Freezes the machine-interface semantics for M3 before any REST or MCP runtime is implemented. This is a documentation/contracts-only change.

  • New authoritative contract: docs/machine-interfaces.md
    • REST v1 surface frozen to exactly GET /healthz, GET /v1/status, POST /v1/select, POST /v1/simulate (earlier /v1/providers ideas deferred with rationale);
    • typed request shapes mirroring the application inputs (profile XOR explicit requirement; tightening only with profile), returning the existing CapacitySnapshot v3 / SelectionDecision / SimulationResult serializations inside a small {"schema_version": 1, ...} envelope;
    • error semantics: a valid no-solution is HTTP 200 (selected = null, no_eligible_candidate); invalid client requests are HTTP 400 with a safe closed-vocabulary envelope (invalid_request); application failures are HTTP 500 (internal_error); provider telemetry degradation remains domain data, never a transport error;
    • side-effect wording frozen: selection issues no model prompt and does not intentionally consume inference quota; capacity collection may exercise the bounded D-018 recovery;
    • security/lifecycle: loopback-only default bind, no authentication layer (because loopback-only local interface), no credentials/endpoints/prompts through the interfaces, single local process, no cache/database/history;
    • MCP: exactly scarcity_status, scarcity_select, scarcity_simulate over stdio, calling the application layer directly in-process — no REST runtime dependency;
    • versioning boundaries kept separate (capacity v3 vs envelope v1 vs catalog/policy versions) and the CLI/REST/MCP parity requirement frozen.
  • docs/decisions.md: new D-028 recording the full contract; U-008 narrowly resolved for M3 (scarcity_router remains the stable Python module identity; final branding deferred).
  • docs/architecture.md / docs/roadmap.md / README.md: interface sections updated to the frozen surface, M3 sequence frozen (M3a → M3b → M3c → closeout; M3b waits for this merge; M3c never depends on REST runtime), documentation map linked.

Why

CLI selection/status semantics were accepted under M2; transport-specific code must not invent or diverge from that behavior. Freezing envelopes, error classes, security boundaries and parity first keeps the transports thin and makes parity testable.

Explicit non-scope

No REST/MCP runtime, no HTTP listener, no MCP SDK or framework dependency (pyproject.toml untouched), no authentication, no model execution, no prompt proxying, no product source or test changes.

Validation

  • git diff --check: clean
  • uv sync --only-dev + make check: 796 tests pass, basedpyright 0 errors / 0 warnings / 0 notes
  • git diff --stat origin/develop...HEAD: 5 files, docs only (README.md, docs/architecture.md, docs/decisions.md, docs/machine-interfaces.md, docs/roadmap.md)
  • git log --merges origin/develop..HEAD: empty (no merge commits); branch is one commit ahead of origin/develop (49115c6)
  • origin/develop unchanged by the feature-branch push (49115c6 before and after)
Closes #45 (via human merge gate; the issue closes when this PR is merged). ## What M3a — REST/MCP planning gate. Freezes the machine-interface semantics for M3 **before** any REST or MCP runtime is implemented. This is a documentation/contracts-only change. - **New authoritative contract:** `docs/machine-interfaces.md` - REST v1 surface frozen to exactly `GET /healthz`, `GET /v1/status`, `POST /v1/select`, `POST /v1/simulate` (earlier `/v1/providers` ideas deferred with rationale); - typed request shapes mirroring the application inputs (profile XOR explicit requirement; tightening only with profile), returning the existing `CapacitySnapshot` v3 / `SelectionDecision` / `SimulationResult` serializations inside a small `{"schema_version": 1, ...}` envelope; - error semantics: a valid no-solution is HTTP 200 (`selected = null`, `no_eligible_candidate`); invalid client requests are HTTP 400 with a safe closed-vocabulary envelope (`invalid_request`); application failures are HTTP 500 (`internal_error`); provider telemetry degradation remains domain data, never a transport error; - side-effect wording frozen: selection issues no model prompt and does not intentionally consume inference quota; capacity collection may exercise the bounded D-018 recovery; - security/lifecycle: loopback-only default bind, no authentication layer (because loopback-only local interface), no credentials/endpoints/prompts through the interfaces, single local process, no cache/database/history; - MCP: exactly `scarcity_status`, `scarcity_select`, `scarcity_simulate` over stdio, calling the application layer directly in-process — no REST runtime dependency; - versioning boundaries kept separate (capacity v3 vs envelope v1 vs catalog/policy versions) and the CLI/REST/MCP parity requirement frozen. - **`docs/decisions.md`:** new D-028 recording the full contract; U-008 narrowly resolved for M3 (`scarcity_router` remains the stable Python module identity; final branding deferred). - **`docs/architecture.md` / `docs/roadmap.md` / `README.md`:** interface sections updated to the frozen surface, M3 sequence frozen (`M3a → M3b → M3c → closeout`; M3b waits for this merge; M3c never depends on REST runtime), documentation map linked. ## Why CLI selection/status semantics were accepted under M2; transport-specific code must not invent or diverge from that behavior. Freezing envelopes, error classes, security boundaries and parity first keeps the transports thin and makes parity testable. ## Explicit non-scope No REST/MCP runtime, no HTTP listener, no MCP SDK or framework dependency (`pyproject.toml` untouched), no authentication, no model execution, no prompt proxying, no product source or test changes. ## Validation - `git diff --check`: clean - `uv sync --only-dev` + `make check`: 796 tests pass, basedpyright 0 errors / 0 warnings / 0 notes - `git diff --stat origin/develop...HEAD`: 5 files, docs only (README.md, docs/architecture.md, docs/decisions.md, docs/machine-interfaces.md, docs/roadmap.md) - `git log --merges origin/develop..HEAD`: empty (no merge commits); branch is one commit ahead of `origin/develop` (49115c6) - `origin/develop` unchanged by the feature-branch push (49115c6 before and after)
Freeze the M3 machine-interface semantics before any transport is
implemented (Forgejo issue #45, decision D-028):

- docs/machine-interfaces.md: authoritative REST v1 surface
  (/healthz, /v1/status, /v1/select, /v1/simulate), the three stdio MCP
  tools calling the application directly, error semantics (no-solution is
  HTTP 200; closed invalid_request/internal_error vocabulary), side-effect
  wording inheriting D-018, loopback-only binding without an auth layer,
  version boundaries, parity requirement and design scenarios.
- docs/decisions.md: D-028 record and U-008 narrowly resolved for M3
  (stable scarcity_router module identity; branding deferred).
- docs/architecture.md, docs/roadmap.md, README.md: frozen M3 sequence
  (M3a -> M3b -> M3c -> closeout) and documentation links.

Contracts only: no product source, test, or dependency change.
Single post-review remediation pass on PR #46 (issue #45); the frozen
surface and contract choices are unchanged.

- Freeze nested-contract version compatibility: the machine-interface
  contract includes the envelope AND the exposed domain documents; an
  incompatible nested-domain change requires a v1 compatibility serializer
  or a new major version; version-number-only bumps flow through when the
  wire shape stays compatible.
- Freeze request parsing strictness (duplicate JSON keys, NaN/Infinity
  constants -> 400 invalid_request), exact missing/null field semantics
  (replenishment_states null -> invalid_request; nested simulation
  override tri-state preserved), exactly-one requirement source, unknown
  profile_id -> invalid_request, and the logical MCP error payloads
  (invalid_request / internal_error).
- Correct D-028 and U-008 dates to the UTC convention (2026-09-06) and
  align the roadmap M3a gate dates.

Docs only: no source, test, or dependency change.
adrian.tkacz deleted branch m3a-interface-contracts 2026-09-06 23:46:40 +00:00
Sign in to join this conversation.
No description provided.