Skip to content

Gate BMF payload versions per project - #1016

Draft
epompeii wants to merge 1 commit into
u/ep/parameters-api/bmf-versionfrom
u/ep/parameters-api/project-gate
Draft

Gate BMF payload versions per project#1016
epompeii wants to merge 1 commit into
u/ep/parameters-api/bmf-versionfrom
u/ep/parameters-api/project-gate

Conversation

@epompeii

@epompeii epompeii commented Aug 26, 2026

Copy link
Copy Markdown
Member

Each project now declares the highest BMF payload version it accepts.

The field

bmf_version is a project field. It defaults to 0 for every project, new and
existing, and it is visible to anyone who can see the project. Only a server admin
can move it: a PATCH /v0/projects/{project} that carries bmf_version from anyone
else is refused, and a PATCH that does not carry it is the patch it has always
been for that caller. Nothing ratchets, so an admin can lower the gate again just as
easily as they raised it.

The gate is a maximum, not an exact match. A project at version 1 still ingests a
payload that declares version 0 and a payload that declares no version at all, so
raising the gate refuses nothing that ingested before it moved.

The two enforcement points

A payload states its version twice, and each statement is checked against the same
gate. Both refusals are the same class and both name both versions, the payload's
and the project's, so the message says what was sent and what the project would
take.

  1. Declared. The bmf_version key a payload declares is checked before anything
    at all is created for the report. This matters beyond the results: later layers
    hang payload shapes off the declared version, and none of them should reach a
    project that does not accept them.
  2. Parsed. The version the results actually parsed as is checked after parsing.
    A payload can reach a v1 leaf without declaring anything: the json_v1 adapter
    names the leaf outright, and the magic and json nodes fall back to it. Checked
    only on the declared key, the gate would be decorative.

The second check is a deliberate behavior change for those two undeclared paths on a
project still at version 0. Neither is a documented way to send v1 results, and a
gate a payload can walk around is not a gate.

Report ingest, /v0/run, and a job based run share one check rather than three
copies of it. A job declares nothing, since its results are the runner's own output
rather than a submitted payload, but the gate still applies to what those results
parse as.

One carve-out sits ahead of both checks: the idempotency lookup. A replay of a previously
accepted request returns the existing report even if an admin lowered the gate in between.
Idempotency keys exist for retry safety, a replay creates nothing new, and refusing one would
report a completed ingestion back to the client as a failure. A fresh request, with no matching
key, still meets the gate before anything is created.

Tested

A new project reads back at 0. A declared version above the gate is refused on both
the report route and /v0/run. So are v1 results with no declared key, through the
default adapter and through the explicit json_v1 leaf, both with the same refusal.
An admin raising the gate admits every one of those payloads and an admin lowering
it turns them away again. A raised project still takes version 0 payloads, declared
and absent. A non-admin carrying the field is refused, and the same patch without
the field succeeds.

@epompeii
epompeii force-pushed the u/ep/parameters-api/project-gate branch from 6d81122 to 4a7733e Compare August 26, 2026 06:31
@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

ProjectBencher
Branchu/ep/parameters-api/project-gate
Testbedintel-v1
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
microseconds (µs)
(Result Δ%)
Upper Boundary
microseconds (µs)
(Limit %)
Adapter::Json📈 view plot
🚷 view threshold
5.25 µs
(+10.56%)Baseline: 4.75 µs
5.51 µs
(95.36%)
Adapter::Magic (JSON)📈 view plot
🚷 view threshold
5.07 µs
(+10.06%)Baseline: 4.61 µs
5.29 µs
(95.84%)
Adapter::Magic (Rust)📈 view plot
🚷 view threshold
27.66 µs
(+6.21%)Baseline: 26.05 µs
28.17 µs
(98.19%)
Adapter::Rust📈 view plot
🚷 view threshold
4.67 µs
(+27.19%)Baseline: 3.67 µs
5.21 µs
(89.53%)
Adapter::RustBench📈 view plot
🚷 view threshold
4.66 µs
(+27.03%)Baseline: 3.67 µs
5.19 µs
(89.75%)
🐰 View full continuous benchmarking report in Bencher

@epompeii
epompeii force-pushed the u/ep/parameters-api/project-gate branch 2 times, most recently from ae947aa to f9f485d Compare August 26, 2026 08:01
@epompeii
epompeii force-pushed the u/ep/parameters-api/project-gate branch from f9f485d to c420967 Compare August 27, 2026 03:46
Each project now declares the highest BMF payload version it accepts. The field is
`bmf_version` on the project, it defaults to 0 everywhere, and it is visible to
everyone that can see the project. Only a server admin can move it: a PATCH that
carries the field from anyone else is forbidden, and a PATCH that does not carry it
is the patch it has always been. The gate is a plain setting, so an admin can lower
it again just as easily.

The gate is a maximum, not an exact match. A project at version 1 still ingests a
payload that declares version 0 and a payload that declares nothing at all, so
raising the gate refuses nothing that ingested before it moved.

Ingest checks the gate twice, once for each way a payload states its version, and
both refusals are the same class and name both versions, the payload's and the
project's. The `bmf_version` key a payload declares is checked before anything is
created for the report, because later layers hang payload shapes off the declared
version and none of them should reach a project that does not accept them. The
version the results actually parsed as is checked after parsing, because a payload
can reach a v1 leaf without declaring anything: the `json_v1` adapter names the leaf
outright, and the `magic` and `json` nodes fall back to it. Without the second check
the gate would be decorative.

That second check is a deliberate behavior change for those two undeclared paths on
a project still at version 0. They are not a documented way to send v1 results, and
a gate a payload can walk around is not a gate.

Report ingest, `/v0/run`, and a job based run share one check rather than three
copies of it. A job declares nothing, since its results are the runner's own output
rather than a submitted payload, but the project gate still applies to what those
results parse as.
@epompeii
epompeii force-pushed the u/ep/parameters-api/project-gate branch from c420967 to 92a351f Compare August 27, 2026 05:11
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