Skip to content

release: spec-kit ae602f5 to main - #35

Merged
jm-rivera merged 11 commits into
mainfrom
release/2026-10-29
Oct 30, 2026
Merged

jm-rivera merged 11 commits into
mainfrom
release/2026-10-29

Conversation

@jm-rivera

Copy link
Copy Markdown
Contributor

Releases spec-kit at ae602f5 to main, cut on release/2026-10-29 so the dashboard contract (#30, already in dev) ships next release: client-contract changes (#21, #23, #24, #25, #26, #33, #34), the capture tool's long-build pick (#28), registry changes (#13, #29), and the licence and public docs (#7).

The registry adds two codes, requested_option_unavailable and conflicting_requested_options, and maps the Presentation write refusals at the write routes' statuses. #33 and #34 name the file selector's query keys (format, bundle, layout), judge every column, flat or nested, by one value rule, and close base-URL and client-identity cases where Python and WHATWG clients disagreed.

dfo-apis pins ae602f5 on dev (apis #189), and the apis release moves main's pin there from 0d19172, so merge this first, with a merge commit, so ae602f5 is reachable from main. dfo-ai pins 708f293 on both branches, which main already holds.

jm-rivera and others added 11 commits October 25, 2026 18:51
…record at apis 99e62038c (#26)

served/openapi.json and SOURCE.json come from dfo-apis 99e62038c.
NoDataOption and SentenceToken carry entity_choice, the index into
no_data.ambiguous_entities of the group an option settles or a slot
asks, ParsedEntity carries an optional name, and Part.entity_refs has
one entry per restatement row. The 32 recorded scenarios are recorded
at that commit, including search-place-ambiguous-twice, two
entity_ambiguous groups of one text. The long-build questions move to
1964 and 1965, which dev had not built, and the poll-pacing derivation
cycles its waits over any recorded build of three or more running
polls.

Co-authored-by: Claude <noreply@anthropic.com>
…tatuses (#13)

* fix(registry): map Presentation write refusals at the write routes' statuses

On presentation.write, binding_invalid, constraint_violation and
resource_exhausted map at 400, frame_unavailable at 404 and
release_mismatch at 503, the statuses the Presentation write routes
serve. presentation.render keeps 422 and 409.

frame_unavailable's definition names the causes it covers: a frame never
stored, expired, revoked, or whose stored artifact is missing,
quarantined, or fails its digest check. A frame store that fails to
answer is service_unavailable, which presentation.write and
presentation.render already map at 503.

* fix(registry): map edit_conflict on presentation.render

The render routes read the stored revision they draw, and that read
refuses edit_conflict (409) when the component has no current revision.
edit_conflict maps to presentation.render at 409, and its definition
names that case beside a revise of a revision that is no longer current.

* docs(registry): name every case frame_unavailable covers

frame_unavailable's definition names each case Presentation refuses
with it: a frame never stored or published, expired, or revoked, a spec
in quarantine, and a stored artifact that is missing, fails its digest
check, does not decode, or holds more rows than Presentation loads.
…son (#21)

A no_data part becomes a gap of its reason's gap_kind whatever
diagnostics.timed_out says. Search marks every part it left unresolved at
its time limit with the reason timed_out, so a part it answered before the
limit keeps its reason, its message and its choices.

answer-timed-out now carries reason timed_out on the unfinished part, and
answer-timed-out-keeps-reasons pins a resolved part that keeps
variable_not_resolved under a timed-out answer. The validator's
answer_gaps reads the part's reason alone, and a new validator test pins
that a diagnostics that is not an object holds no warnings.
…se URL rule (#23)

A POST for a build identical to one pending or running returns that
operation, so a client that stops waiting can post again to join it.
fetch waits with no whole-call deadline by default, and a download that
delivers no byte for 30 s is a retryable network_error. The new base-url
rule orders the argument, DFO_BASE_URL and the public default, with 14
vectors replayed by a reference resolver in tests/test_rules.py.
…lumns for reading (#24)

A fetch now posts "without_party_totals": true unless the caller passes
party_totals (Python) or partyTotals (TypeScript). A role the caller binds
keeps what it binds. The wire flag is unchanged; only the client default moves.

The new column-order rule (rules/column-order.json, nine slots with vectors)
puts time period, provider, recipient, subject, purpose, value, unit, flow type
and provenance first, then the served order, with stat_var last. A client
hashes the downloaded file before it orders the columns, so digests and refs
keep the server's file.

Every scenario is re-recorded against apis dev at c9a1b905, and
served/openapi.json is recaptured. Two new recorded scenarios,
fetch-spec-party-totals-left-out and fetch-spec-party-totals-kept, pin the
default and the option.

Every accepted ref example now carries the rendering, parts, content
version and sentence of a member its scenario served, so ref-one-member
pins the plain rendering from fetch-spec-party-totals-kept and
ref-answer-member comes from answer-definite-fetch. The validator checks
both, with a fault row each.

Every scenario is re-recorded against the deployed dfo-apis dev app at d80df9f5f, with the served document captured from that commit. The long-build scenarios start at 1967 and 1968, years dev had not built.
…he base URL and role keys (#25)

The client-identity rule (rules/client-identity.json, 18 vectors) has every
API request carry X-DFO-Client: <client>/<version> (<runtime>), such as
dfo-python/0.1.0 (CPython 3.14) or dfo-ts/0.1.0 (browser). A caller's
headers or header provider naming it raise header_invalid, and no request to
another origin, a signed storage URL included, carries it. Scenario records
keep no request headers, so a client's replay checks that each path and
origin_path request carries the header and each url request does not.

The column-order rule becomes reading-order (rules/reading-order.json, 10
vectors with rows). Columns run: the filled reading slots, then the other
columns whose values differ between rows, then the constant columns, with
stat_var last. A stack puts part first and judges the groups over the
stacked table.

Each member's rows are sorted by the filled slots without value and the
time period, then by the varying columns (stat_var among them when it
varies), then by the time period, so each series' years read together.
Keys sort ascending with nulls last, ties keep served order, and members
keep member order.

The validator reads served Parquet once per file through served_table and
checks every scenario's columns and every vector's rows. 28 scenarios
change their columns only; no body file changes, so digests stay the
server's.

A base URL is valid when it is an absolute http or https URL with a host
and no userinfo (an empty one included), query, fragment, dot segment,
character outside visible ASCII, or IPv4 host in a form other than four
decimal numbers. A relative URL is valid only in a TypeScript client inside
a browser page, which resolves it against the page's address, and a valid
value is used less its trailing slashes. Seven new vectors in
rules/base-url.json pin these cases.

The User-Agent sentence now says TypeScript sends dfo-ts/<version> outside
a browser page or browser worker, in Node, Deno, Bun or a server runtime
such as Cloudflare Workers.

The role-keys rule (rules/role-keys.json, 13 vectors) names the keys of
entities=: place for the subject, posted as observationAbout, and origin
and destination for flows, posted as themselves. Spec.roles and
specRoles(spec) list a spec's roles under those keys, a flow spec takes
both directions, and a key outside the spec's roles raises
invalid_argument naming the typed key and the spec's roles, so
observationAbout and column names such as financingRecipient are refused.
A spec that names no role posts place as observationAbout and leaves the
rest to the server.
mint-async-build and operation-resume need a 202, which only an unbuilt
spec gives, and each live record builds its two specs for good. The tool
named their start years as constants, so every record needed new years
by hand. Context.async_spec now walks start years 1960 to 2000 for the
DAC and OECD donor questions and takes the first spec that is one
definite part, sits just above the sync budget, repeats no earlier pick,
and has no version on the versions route. It refuses when the range is
used up.

The party-totals scenarios narrow a built DAC spec and need no unbuilt
one, so they keep their own question, PARTY_TOTALS_FLOW.

Co-authored-by: Claude <noreply@anthropic.com>
* feat(registry): register the two scope-choice refusal codes

Add requested_option_unavailable and conflicting_requested_options as
search refusals on the dataset_resubmit door (problem details, 422), in
the registry and its packaged mirror.

* fix(registry): define the scope-choice refusals as Search raises them

Each definition covers every case dfo-apis refuses with it: an axis the
part does not offer, an option the axis does not list or an unavailable
one, and entries that conflict or rule out every option of an axis. The
client contract gives both a fix_request row.
#33)

* fix(client-contract): ask the file selector only for the keys it reads

A file-selector rule names format, bundle and layout; the validator, capture tool and selector scenarios derive the query from it. The validator compares values by one rule and reports malformed vectors, and the base-URL and client-identity rules close the cases where Python and WHATWG clients disagreed.

* fix(client-contract): read a resumed build's rendering and keep nested columns out of sort keys

A resumed operation's result takes the rendering its build posted, and refs are checked against the build that served their file. List and struct columns never sort, the reference same_value is the validator's, selector queries lowercase booleans only, and the capture's OECD prerequisite keeps party totals.
… same_value (#34)

The nested-cells vector's flags column holds one item type, the validator reports a vector column Arrow cannot build, and served_table judges flat columns by same_value as it does nested ones.
@jm-rivera
jm-rivera merged commit 3cb209d into main Oct 30, 2026
57 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant