Repository navigation
Approval ID aliases bypass retained meta promotion headroom #85
Description
Reproduced defect
At PR #84 head eab900b02d1652884bdde10f12499340005cd4ed, an ordinary loop can consume promotion capacity retained by a meta programme simply by spelling the same approval as RL-APPROVAL or approval instead of rl-approval. The SDK resolves all three to the same Decision, but the new retained-capacity accounting compares the raw approval string with the canonical identity stored in reservations.
This is additional evidence beyond the resolved promotion-time capacity review: the existing concurrent regression uses the canonical approval spelling throughout.
Goal and source evidence
- The accepted meta feature requires fail-closed promotion and idempotent accounting. The current design says every promotion preserves unconsumed meta headroom under the shared writer lock
- metaReservations filters with
record.approval === approval;reserveMetapersists the SDK-resolved approval identity - promoteGenerationCore passes its uncanonicalized approval input to the retained-capacity helper. Reading the approval successfully does not replace that input with
item.id - The same raw identity is also used in consumed-promotion comparison, so resolving only reservation matching would leave inconsistent accounting for persisted alias spellings
- The pinned SDK
2026.10.13already lowercases IDs and supplies the workspace prefix; its item lookup applies those normalizers. This is static corroboration of the alias contract, not a claim that the end-to-end reproduction ran on that pin
Sanitized reproduction
The following uses only the repository's synthetic committed-history fixture and ordinary numerical bandit loop. Save as test/review-approval-alias.test.ts in a disposable checkout of the tested commit and run node --test test/review-approval-alias.test.ts.
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { test } from "node:test";
import { runRlMeta, runRlLoop, type JsonValue } from "../index.ts";
import { retainedMetaPromotionHeadroom } from "../meta.ts";
import { metaWorkspace, metaConfig, metaSpec } from "./fixtures/meta-workspace.ts";
test("approval aliases preserve reserved promotion capacity", async () => {
const w = await metaWorkspace();
const alias = w.approval.toUpperCase(); // Also reproduced with "approval"
assert.equal((await w.client.get(alias)).item.id, w.approval);
await w.client.update(w.approval, {
body: "```json\n" + JSON.stringify({ ...metaSpec, permitted_promotions: 4 }) + "\n```",
});
await assert.rejects(runRlMeta(w.client, w.coordinates, {
id: "reserved", config: { ...metaConfig, meta_generations: 1, candidates: 1 },
dataset: w.dataset, approval: w.approval, proposer: "seeded",
onStep(key) { if (key === "programme") throw new Error("capacity held"); },
}), /capacity held/);
assert.equal(await retainedMetaPromotionHeadroom(w.client, w.approval, []), 4);
const raw = JSON.parse(readFileSync(
new URL("../examples/loop-bandit.json", import.meta.url), "utf8",
)) as Record<string, JsonValue>;
const report = await runRlLoop(w.client, w.coordinates, {
id: "ordinary-alias", config: { ...raw, max_generations: 1 }, approval: alias,
});
assert.equal(report.promoted, 0, "all four slots are reserved");
});Observed independently in fresh synthetic workspaces:
- Canonical
rl-approvalcontrol: retained headroom 4,promotion_refused, 0 promoted; 1/1 passes - Uppercase
RL-APPROVAL: SDK resolvesrl-approval, helper returns headroom 0,generation_limit, 1 promoted; 0/1 passes, assertion1 !== 0 - Short
approval: same canonical resolution, headroom 0 and 1 promotion; 0/1 passes, assertion1 !== 0
Expected: all equivalent spellings identify one approval budget; with all four slots reserved, an ordinary promotion is refused. Actual: one ordinary promotion succeeds beside four still-retained slots under a four-slot approval.
Impact: the shared-approval capacity guarantee depends on caller spelling and can be bypassed through the public SDK loop input. This establishes reserved-capacity oversubscription. No claim is made here about an additional live-provider run or a particular eventual total after resuming the owner.
Verification limits
Reproduced directly from the named production sources on Node 24.19.0 with the already-available SDK 2026.10.18. Repository pin is 2026.10.13 and hosted CI uses Node 22/26; no pinned-runtime full-suite certification is claimed. The pinned SDK alias behavior above was inspected statically.
Nine unchanged focused regressions at the tested head pass 9/9, including ordinary concurrent promotions, shared headroom, interrupted retained-capacity replay, canonical reservation lookup, classified refusals, excluded-source case folding and packaged receipts. The new alias tests fail while changing only their approval spelling to the canonical ID passes.
Tests ran in disposable copies with isolated HOME/config/tmp, telemetry disabled, network/fetch and remote Git blocked, and only inspected synthetic fixture/local trainer processes allowed. No provider inference, live datasets, dependency changes, merge, release or deployment was performed. Exact-head CI run 39133504297 was still running at filing preparation; historical 622-test results are not evidence for this head.
Acceptance / regression criteria
- Resolve and validate approval references to one canonical SDK item identity before reservation matching, consumed-promotion accounting and new promotion persistence; preserve fail-closed behavior if that identity cannot be established
- Cover full canonical, uppercase and short-ID references against the same Decision, including mixed spellings across ordinary/manual and meta flows
- With all capacity reserved, each alias must refuse ordinary promotion; with one free slot, concurrent mixed-alias callers must consume at most that one slot
- Account safely for existing persisted approval aliases rather than treating their promotions as a fresh budget
- Preserve the canonical owning-inner-generation exemption, atomic writer-lock checks, owner resume and terminal replay without extra charges
- Run the new tests on pinned SDK 2026.10.13 and the unchanged npm/Bun release gates; retain a behavior-only revert proof
Deduplication checked all 84 existing issue/PR entries across open and closed states, all nine current PR review threads, and all 89 canonical PM records at the tested head. The programme-ID case-folding fix and the resolved general retained-capacity finding do not cover this approval-reference mismatch.
Activity
unbraind commentedon Oct 18, 2026
Fixed in pushed commits
49323f0b24092ce4a34d535fbf64e41e28588e59and5c9ab9a2a756bf626443b53f56127738ea85ef36. Approval reads retain the canonical SDK Decision id; writer-locked promotion accounting uses it for both held reservations and consumed generations, and newly promoted records persist it. Existing generation and reservation aliases resolve through the same SDK boundary, with unreadable or wrong-type approval references refused. Loop replay compares canonical identities while retaining original durable receipt bindings.On pinned SDK 2026.10.13,
node --test --test-name-pattern="approval aliases" test/meta.test.tspasses 2/2, also through the pinned PM runner. Canonical, uppercase and short references refuse ordinary and manual promotions beside four held slots; mixed-alias contenders share exactly one free slot beside four held slots under a five-slot approval. Tests check canonical new persistence, historical aliases, owner/terminal replay and no extra process charges. Reverting only the changed identity-handling functions to eab900b leaves both regressions in place and fails 0/2; restoring the fix passes 2/2.The PR body includes
Fixes #85. Complete isolated npm/Bun gates are running on the new source; prior 626/626 results are explicitly attributed to eab900b. No thresholds, dependencies or frozen live receipts changed.unbraind commentedon Oct 18, 2026
Final pushed head
ef8210b1a1d534653c6d2d5194de699a584381c7is verified by exact-head CI: complete npm gates on Node 22/26 and the Bun gate on Node 26 each pass 628/628, zero failures/cancellations/skips, all four coverage metrics 100%, all 35 sources and 295 documented declarations. CodeQL passes.The strengthened concurrency test guarantees every contender uses an alias. Restoring only raw approval persistence fails 0/1 (RL-APPROVAL versus canonical rl-approval); reverting all changed identity-handling functions to eab900b fails 0/2. Restored pinned-SDK tests pass 2/2. The PR body includes Fixes #85; the issue is left open for orchestrator verification. No thresholds or frozen live receipts changed.