Repository navigation
feat: land the M2 second breadth batch — session lifecycle, the Shells family, and the location design - #38
Merged
Conversation
CheckStatusShape admits exactly one success of 200 or a content-free 204; the envelope plan records its success status and carries null payload names for the new NoContent kind; envelopes emit payload-free records, adapters emit the declared-status arm and refuse a 204 arriving with a body via the new NonEmptyNoContentFailure on the adapter spine. Fixture and snapshots grow the delete-shaped no-content operation.
The {location,data} wrapper — classified at ingestion since the census — now
binds fail-closed in object and list variants: the wrapper joins the excluded
response roots, the required location sibling must reference a named component
schema (promoted inline refused, mirroring the cursor-list item wall), and the
envelope plan records the sibling type. Envelopes surface a guarded Location
property beside the payload, DTOs read both members in one pass, adapters
project through the generalized sibling helper, and 'Location' joins the
reserved payload names.
The binder admits exactly one deep-object shape — the optional nullable
{directory?, workspace?} selector, matched structurally — as the new Location
query kind typed by the hand-written LocationSelector spine record; every other
deep-object stays refused. QueryStringBuilder.AddLocation writes the
location[directory]/location[workspace] keys with literal brackets matching the
first-party client's serialization, route builders emit the call, and query
records surface the selector property. PublicApi gains the spine type.
…pshot (Q94) OpenCodeClientOptions gains the LocationSelector ambient default; the pipeline snapshots it at construction and rides the x-opencode-directory/x-opencode-workspace middleware channel verbatim — the server resolves any explicit per-request query location first, so no client-side merge exists. The selector refuses blank members at init on both channels, and the shared recorder captures request headers.
A body+query operation now binds when every query property is the location selector: the query plan rides the request body model (RidesRequestBody), the body model gains JsonIgnore query-side properties attached at composition, the route builder types itself with the body model and the operation method's one request parameter feeds both the route and the JSON path. Every other mix keeps the deliberate refusal, and standalone query records are untouched.
… Shells family Seven operations land on the sealed design-prover surface: RemoveSessionAsync and RenameSessionAsync complete the session lifecycle over the 204 machinery, and the Shells family (list/create/get/remove/timeout) exercises every Arc 2 seal — deepObject location selectors, the merged body+query ShellCreateRequest and ShellTimeoutRequest, location-echo envelopes, and the first PATCH/DELETE verbs (PATCH rides the internal OpenCodeHttpMethod spine, absent downlevel). The verb vocabulary gains remove/rename/timeout, RequestTypeName folds Get like the response name, reachability recollects post-alias so Shell.Info's promoted inline children stay bound, the alias comparer resolves promoted references structurally, Shell.Info1 collapses through the second schemaAliases row, the Shells IDE0130 arbitration glob extends per its own rule, Extensions registers the family the roster test demanded, and the contract matrix covers the new surface. shell.output defers to a later batch pending two mechanisms of its own.
…dary The fifth curation section declares two query parameters one operation's server refuses to combine — spec prose the binder cannot derive. The validator owns the row shape (pairs only, reason required, selected-profile membership), the binder pins each name to the bound query surface and records the pair on the plan, the route builder throws ArgumentException before any request is built, and message.list's order+cursor pair rides the first row with a no-send contract test. Closes #28.
The server decodes the directory header (decodeURIComponent with a raw fallback) while reading the workspace header verbatim, so the escaping mirrors that asymmetry exactly. Measured against a live server whose cwd differed from both probe targets: a directory literally named 'loc%20test' returns HTTP 500 with an empty body when the header rides raw — the server decodes '%20' into a space and resolves a directory that does not exist — and returns 200 with the correct location when the value is escaped. Escaping also keeps a non-ASCII path sendable at all, since header values cannot carry it raw.
…ntation Session 26 (Q95) records what the upstream sweep and the live probes found: the header channel is not deprecated — it was never expressible in a schema derived from declared endpoint parameters, the same reason Authorization is absent — precedence resolves per field rather than per location object, and the middleware is attached per group so the ambient channel is inert on session-scoped and unmiddlewared operations. The options documentation now states both limits instead of promising a blanket override; doc 15 gains the mechanics; the ambient channel's future is anchored at M5 as #37, where packaging unblocking makes it the last free moment to change the surface.
…alidator QueryStringBuilder's private block reads in first-use order again — Append, reached from AddText, precedes the bracketed variant AddLocation reaches. In CurationValidator the new mutual-exclusion validator had landed between the alias walls' documentation and the method it describes, leaving one method with two summaries and the other with none; it now sits last among the privates, matching its position in the Validate call sequence.
…ith no query surface The row wall lived behind the query-request binder, which returns early when an operation declares no query parameters — so a row naming such an operation went dormant instead of refusing, unlike the property-override and schema-alias drift contracts. The wall now answers for that case too, which is the shape a spec refresh would produce by dropping an operation's query parameters.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Executes the plan in
docs/superpowers/plans/2026-08-15-m2-second-breadth-batch.md— the design-prover batch: every mechanism the M3 Arc 2 seals (Q93/Q94) introduced is exercised by a real operation. Closes #28.Machinery
200or a content-free204; the envelope plan records its success status; responses emit payload-free records and adapters refuse a 204 that arrives with a body.{location, data}envelope profiles, object and list variants. The requiredlocationsibling must reference a named component schema (promoted-inline refused, mirroring the cursor-list item wall) and surfaces as a guardedLocationproperty beside the payload.{directory?, workspace?}selector, matched structurally — typed by the hand-writtenLocationSelectorspine; every other deep-object stays refused.*Requestexactly when every query property is the location selector; the model gains[JsonIgnore]query-side properties and the route builder types itself with the body model. Every other mix keeps the deliberate wall.OpenCodeHttpMethodspine becauseHttpMethod.Patchdoes not exist downlevel.mutuallyExclusiveQueriescuration section — fail-closed validated, pinned to the bound query surface, refused at the route boundary before any request is built.Surface
Seven operations join the four already callable:
session.remove,session.rename, and theShellsfamily (list/create/get/remove/timeout). The verb vocabulary gainedremove/rename/timeout,RequestTypeNamefoldsGetlike the response name, reachability recollects after alias collapse soShell.Info's promoted inline children stay bound, the alias comparer resolves promoted references structurally, andShell.Info1collapses through a secondschemaAliasesrow. Extensions registers the family the roster test demanded.v2.shell.outputis deferred — its inline data object and integer cursor query parameters each need a mechanism no other admitted operation needs.Verification
All six gates ALL-GREEN at every commit; 1042 tests. Demonstrated live against
opencode2 servev0.0.0-next-17403: shell create → get → timeout → remove and session rename → remove, 204s typed, the ambient location echoed back.Review, and what it caught
A three-dimension adversarially-verified review ran on the branch. Runtime-correctness surfaced a genuine wire defect that the earlier demo had missed because its path contained no special characters:
The ambient directory header rode the wire raw, but the server percent-decodes it. Measured against a live server whose cwd differed from every probe target, a directory literally named
loc%20testreturns HTTP 500 with an empty body raw — the server decodes%20into a space and resolves a directory that does not exist — and 200 with the correct location when escaped. Fixed in431293b.That finding triggered a full upstream tour (research log Q95), which also settled three mechanics the documentation had stated too loosely and now states correctly: precedence resolves per field, not per location object; the middleware is attached per group, so the ambient channel is inert on session-scoped and unmiddlewared operations; and the headers are absent from the schema because they were never expressible in it —
OpenApi.fromApiderives parameters from declared endpoint schemas, which is whyAuthorizationis equally invisible while remaining mandatory.The remaining findings — member ordering in two files, and a curated row that could go dormant on an operation with no query surface — are fixed in
e9a61f3andc521105.Follow-up
#37 anchors the ambient channel's future at M5, decision-first: packaging unblocking freezes the public surface, making it the last free moment to drop
OpenCodeClientOptions.Locationor fold it into the query channel.