feat(setup): attest and promote from the browser, on the axis that turned out to be the right one - #469
Merged
Merged
Conversation
…rned out to be the right one
Two JUDGEMENT steps live in the PAPER stage -- attesting an asset, promoting a rule -- so the rule
I wrote two changes ago ("no judgement step may ever be an action") did not make anything safer.
It made a terminal-free paper deployment impossible, which is the entire point of the milestone.
This fixes the rule, not the goal.
THE INVARIANT MOVES TO WHERE IT ALWAYS BELONGED: the capability registry. Not one of the eleven
actions in `keel/capabilities.py` may be reachable from `keel/web/`, and that was already scanned
and pinned independently. `StepKind` was a poor proxy for it, tried twice -- "MECHANICAL only",
then "MECHANICAL or OPERATOR_INPUT" -- and wrong both times. The test now asserts by IDENTITY:
every action's step is checked against the eleven invocations, and `withdrawals attest --enabled`
is named as the judgement step that consequently has no form. That is the line the code already
drew; taste had nothing to do with it.
The step-kind rule becomes the secondary policy it should always have been: **a wizard may record
what the operator supplies; it may never supply it.** An action over a judgement step must declare
inputs, every one required, with nothing pre-filled and no select opening on a valid option.
THAT LAST CLAUSE IS NOT PEDANTRY. `pays_yield` as a checkbox would open unticked, and unticked is
`no` -- the PERMISSIVE answer, since a yield-bearing asset fails the screen (KB §28.4). A form
whose default answer is the compliant one attests on the operator's behalf. So it is a select that
opens on a disabled "choose…" and is `required`: it cannot be submitted without someone answering.
`backing` is the same shape.
Exposing an attestation is safe for a reason its own CLI command states: "an attestation cannot
itself place an order or raise a cap, and the screen it feeds only ever ADMITS to a list that
`guards.py` rail 1 still enforces per-trade". It is not one of the eleven and the CLI imposes no
ceremony on it beyond the mandatory source.
PROMOTION HAS NO FORCE FIELD, and that is load-bearing rather than an omission.
`attempt_promotion`'s docstring is explicit: force "carries no gate HERE ... the O3 contract is the
front-end's to keep, never the service's to assume." The console keeps that contract with a typed
terminal confirmation. A browser cannot, so this front-end simply has no force path -- and
force-promote is itself one of the eleven. `force=False` is hard-wired and pinned by test.
Promotion re-runs a backtest, so it runs as a job for the same reason a fetch does.
The backing choices are literals to keep `keel.compliance.screen` off the CLI's import path; that
duplication is pinned against `KNOWN_BACKINGS`, because a backing kind added there and missing
here would be silently un-attestable from the browser.
VERIFIED END TO END against a live server: three assets attested from the browser and recorded
with exactly the supplied sector, backing, yield answer, source and attester; a submission with a
blank source recorded NOTHING; the checklist then names the assets still outstanding; and
`withdrawals attest --enabled` still shows its CLI command rather than a form.
4087 passed, 3 skipped (5 new). ruff clean repo-wide; mypy clean over keel + packages.
Stacked on #467 -- it builds directly on that PR's `Action`/`ActionInput` and job runner.
Refs #437, #436, #18.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
eaitbrahim
force-pushed
the
feat/d4-judgement-forms
branch
from
August 20, 2026 22:25
e820c87 to
4c9971c
Compare
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 #437 (D4), #436 (D3). Stacked on #467 — retarget to
mainonce that merges.The rule I wrote was wrong, and this fixes the rule rather than the goal
Two
JUDGEMENTsteps live in the paper stage — attesting an asset, promoting a rule. So "no judgement step may ever be an action" made nothing safer. It made a terminal-free paper deployment impossible, which is the entire point of the milestone.assets_attestedrule_promotedsubscription_attestedwithdrawals_attestedThe invariant moves to where it always belonged
Not one of the eleven actions in
keel/capabilities.pymay be reachable fromkeel/web/— already scanned and pinned independently.StepKindwas a poor proxy for it, tried twice ("MECHANICAL only", then "+ OPERATOR_INPUT") and wrong both times.The test now asserts by identity: every action's step is checked against the eleven invocations, and
withdrawals attest --enabledis named as the judgement step that consequently has no form. That is the line the code already drew — taste had nothing to do with it.The step-kind rule becomes the secondary policy it should have been: a wizard may record what the operator supplies; it may never supply it. An action over a judgement step must declare inputs, every one required, with nothing pre-filled and no select opening on a valid option.
That last clause is not pedantry
pays_yieldas a checkbox would open unticked, and unticked isno— the permissive answer, since a yield-bearing asset fails the screen (KB §28.4).A form whose default answer is the compliant one attests on the operator's behalf. So it is a select that opens on a disabled
choose…and isrequired: it cannot be submitted without someone answering.backingis the same shape.Exposing an attestation is safe for a reason its own CLI command states:
Promotion has no force field, and that is load-bearing
attempt_promotion's docstring is explicit:The console keeps that contract with a typed terminal confirmation. A browser cannot, so this front-end simply has no force path — and force-promote is itself one of the eleven.
force=Falseis hard-wired and pinned by test. Promotion re-runs a backtest, so it runs as a job for the same reason a fetch does.Verified end to end
Exactly the supplied values, nothing defaulted, and the one gated step still terminal-only.
4087 passed, 3 skipped (5 new).
ruff checkclean repo-wide,mypyclean.What is still deliberately absent
confirm_cycle— unobservable by construction.🤖 Generated with Claude Code