Repository navigation
feat(admin): the budget split tools, the write guard and the allocation view - #576
Merged
Merged
Conversation
edgehero
force-pushed
the
feat/504-admin-surfaces
branch
2 times, most recently
from
October 4, 2026 14:12
006505b to
d4d16b4
Compare
…on view Part C of issue #504: the operator surfaces for delegated allocation, including the wider write guard issue #504's open question recommended. Tools. dispatch_allocations reads the envelope, the applied split, each project's spend in the envelope window under the worker's own keys (with the key names) and the last 20 alloc:log outcomes, through an allowlist of fields: no plan reason text reaches it. dispatch_priorities_set is the delegated allocation write: sequential, no confirm, works headless, writer operator-session. It calls the worker's applyPlan through read-model.mjs (applyPriorities), fills the basis from the state it reconciled to, keeps _other's current weight when it is left out (any other project left out is plan-incomplete), and returns the outcome, the reason enum, the plan id, clamped and the micro-dollars before and after. dispatch_envelope_set is confirm-gated and refused headless: any subset of window, total, floors, default weights and delegation fields, judged by parseEnvelope before the confirm, then alloc:envelope:expected is set to the new digest BEFORE the file is replaced (tmp and rename, mode and owner kept, a symlink refused), and put back when the file is not written, so the fleet re-bases instead of reading the change as a hand edit. The key is set with GET, and a failed file write puts it back by a compare-and-set, only while it still holds this write's digest, so a second confirmed write that landed meanwhile keeps its key (a live-Valkey race test holds it). The writes connect their fail-fast client before the first command: the lab showed a live Valkey refusing every write otherwise ("Stream isn't writeable"), which the fakes hid; a live-Valkey test now holds it. Cross-check. The project, limit and per-job cap writers (tools and the operator's dialogs and set/unset, each tested) refuse a change that would make a loading envelope invalid (a floored project removed, a row below a floor, the cap removed), before the confirm, in the parser's words; an envelope that already fails does not block them. Write guard. A tool_call handler blocks pi's built-in write and edit whose target is the envelope, projects.json, scoped-limits.json, triggers.json, the settings overlay, the deployment's .env or the deployment pointer, and a powershell command naming one. The set is read per call through the pointer and .env and never shrinks within a session, so pointing .env at a decoy cannot unguard the real file. With no pointer, the .env of the folder pi started in may only ADD to the set (every guarded file it names, under every reading a loader might make of the value, and itself; it configures nothing), so an init plus up deployment with pi started in it is guarded. That .env is read through a bounded read (non-blocking open, a regular file by fstat, at most 1 MiB), so a FIFO or a /dev/zero link a cloned repository carries cannot hang or grow the session. The cwd defaults (./triggers.json, ./scoped-limits.json, ./projects.json) are guarded when their key is set or when that folder is an init scaffold folder, and a repository's own file of that name is otherwise never refused. A deployment the panel cannot see at all is not guarded, a named limit. The tool set is pinned to pi 0.99.1's allToolNames; pi's path rules (Unicode spaces, @, ~, file://, against ctx.cwd) are copied and pinned against its own resolveToCwd. Identity is (dev, ino) for an existing target, and the nearest existing ancestor plus the rest, fully case-folded (APFS folds a long s, the Kelvin sign and the capital sharp s, measured), for a new one, with every link followed; a resolution error blocks. A powershell command matches a guarded name only as a folded path segment, trailing dots and spaces included; an 8.3 short name is a named residual. A real pi session (faux provider) shows a model write, a nested ctx.executeTool write and a codemode write blocked, and an edit of .env and the pointer blocked. bash, a built powershell name and a bash link in the same parallel batch stay named residuals. Commands and panel. /dispatch priorities (no reasons; zero-spend) and /dispatch priorities set <id>=<weight> ..., in USAGE and KNOWN_SUBCOMMANDS. The ALLOCATION view on b: the envelope, each entry's floor, weight, share and spend with its key, the plan's per-project reasons from alloc:plan only and every history cell (escaped, through the control-byte gate), the newest 20 outcomes of alloc:log; r arms an in-frame y/n on the captured row and reverts as operator-revert (no interval, no step, still a CAS); a banner while an envelope-changed-externally row is newer than the last re-base or applied plan; the hint rides the spend divider's meta. PI_ENVELOPE_FILE joins resolvePaths, the panel's .env keys and POINTER_ENV_ALLOWLIST; the setup wizard never writes it. The panel's cron run counts and cost attribution also join a hand-fired run of the trigger (manual:<id>:<millis>, issue #505). Specs: REQ-ADMIN-VIA-PI-EXTENSION (the Statement names the three tools, the Acceptance gains part 1's clauses) and DES-ADMIN-VIA-PI-EXTENSION (the Decision names them, the view, the guard, rejected and residuals) amended, completing #504's same-change spec acceptance across part A and this part; REQ-DELEGATED-ALLOCATION and DES-DELEGATED-ALLOCATION-INSIDE- ENVELOPE name part C; INT-ENVELOPE-FILE-CONTRACT, INT-PRIORITIES-PLAN- CONTRACT and INT-DEPLOYMENT-POINTER-CONTRACT amended; SECURITY.md's session residual widened. REQ-SCOPED-LIMITS, DES-FIRST-RUN-SETUP-WIZARD, INT-HOST-REGISTRY-CONTRACT, INT-RUN-HISTORY-FILE-CONTRACT and CONST-BUDGET-BEFORE-TOKENS unchanged, checked. Refs #504 Signed-off-by: Rob Boerman <robboerman@live.nl>
edgehero
force-pushed
the
feat/504-admin-surfaces
branch
from
October 4, 2026 14:22
d4d16b4 to
33542d4
Compare
7 tasks
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.
Refs #504 (part C, the last of three). Closes #504 together with #573 and #574.
The operator surfaces for the budget split: a model can now set priorities without a keypress, only an operator can change the envelope, and the files that set money and triggers are guarded against pi's own write tools.
Tools
dispatch_allocations(read): the envelope numbers, the current split and spend per project and repo, the plan's id, writer and validity, and the last 20 outcomes. No plan reason text is ever returned to a model.dispatch_priorities_set: sequential, no confirm, works headless. It takes weights (and optional repo weights and a plan life), fills in the basis from the current plan, and applies through the worker's own rules as writeroperator-session. It returns the outcome, the refusal reason, the plan id, whether it was clamped and the split before and after. When a plan leaves_otherout, its current weight is kept.dispatch_envelope_set: behind the operator's confirm, refused headless. It sets the new digest as the expected one, then writes the file atomically through the same parser the worker uses, so the fleet re-bases instead of reporting a hand edit. If the file write fails, the expected digest is put back by a compare-and-set, only while it still holds this write's digest, so two concurrent writes cannot leave the fleet mismatched.The write guard
pi's built-in
writeandedittools, andpowershellby the guarded file names its command contains as whole path segments (folded the same way), are blocked when the target is the envelope,projects.json,scoped-limits.json,triggers.json,settings.json, the deployment's.envor the deployment pointer. A path that was guarded once stays guarded for the rest of the session, so re-pointing a key cannot unguard a file. The working directory'striggers.json,scoped-limits.jsonandprojects.jsonare guarded when their key is set (by the environment, the pointer, or the pointer folder's.env), or, with no pointer, when the folder pi started in is a deployment made byinit. With no pointer, every guarded file that folder's.envnames, and that.envitself, are guarded too; that.envcan only add to the guard, never change what the panel reads. An ordinary repository that happens to hold atriggers.jsonis not affected. A deployment the panel cannot see at all (no pointer, no key set, not the folder pi started in) is not guarded, and SECURITY.md says so. Targets are judged by file identity (a symlink, a hard link, a case variant or a~or@spelling is the file it names), a file that does not exist yet by its parent and its fully case-folded name (so a spelling the file system folds, such as a long s or a capital sharp s, is caught), and a resolution error blocks. Nested tool calls and codemode scripts go through the same guard.bash, names built at run time and the operator's own!commands stay named residuals inSECURITY.md.Commands and panel
/dispatch prioritiesshows the plan, the split and the history;/dispatch priorities set shop=3 platform=1writes one. Both are zero spend.b: the envelope, each project's share and spend, the current plan with its reasons (escaped like every other agent-written text), and the history.ron one of the newest 20 history rows asks y or n in the frame and reverts to it asoperator-revert, skipping the interval and the step. A banner says when the envelope was changed outside the panel, until a later re-base or plan supersedes that edit. Hand-fired cron runs count as runs of their trigger, with their cost attributed to it.Specs
AMENDED:
REQ-ADMIN-VIA-PI-EXTENSIONandDES-ADMIN-VIA-PI-EXTENSION(the three tools by name in the Statement and Decision lists, the guard, the residuals),REQ-DELEGATED-ALLOCATION,DES-DELEGATED-ALLOCATION-INSIDE-ENVELOPE,INT-ENVELOPE-FILE-CONTRACT,INT-PRIORITIES-PLAN-CONTRACT,INT-DEPLOYMENT-POINTER-CONTRACT,SECURITY.md.Release note: the admin imports the worker's new
./allocationexport, so the worker is published before the admin. With #573 this completes the doctrine change #504 asked for.Tests
The headless apply without a dialog, the envelope refusal headless and on decline, the cross-checks, the guard on every file and spelling including nested and codemode writes in a real pi session, reasons escaped in the panel, the
bview, the revert, the banner, and the allocation writes against a live Valkey. Each rule was checked by reverting it and watching its test fail (mutation logs kept with the change's working notes).