feat(boatstack): denials carry their computed solution set - #227
Merged
Conversation
A denial that only states the rule leaves a weaker model retrying the same blocked call. Every guard denial now carries the law's computed solution set: a capped "You can:" list of exact runnable commands legal from the position the finding describes, with owed human inputs marked, plus the full set on the opt-in structured payload (additive keys, schema_version stays 1). A protected-path denial additionally names the verbs that own the attempted path, derived at runtime from the state-ownership map — a verb whose full arguments cannot be derived is named, never fabricated into a command. Enumeration reads only the finding's own fields and the declared tables (the planning enumeration for phase findings, the registry's observe rows, the ownership map for protected paths), so the deny path stays fast and cannot itself fail on unreadable state. Tamper findings now carry the matched managed-path fragment as AttemptedPath (bounded, secret-free) to key the ownership lookup; the host deny contracts take the resolved repo. Conformance: a denial-category totality sweep (every category enumerates picks or sits on a documented exception list), text-guard closure over every pick, phase-bypass picks re-checked against the interlock at the finding's own stage, ownership derivation pinned per subtree, rendering checks in all three modes, and dual-reward corpus routine additions from real enumerations — the constitutional floor is untouched by construction. The corpus immediately caught one stage-inconsistent pick during authoring (record-approval offered at NOT_STARTED), which is exactly the class this law exists to stop. control-law: solution-set-derives-from-guard-declarations control-law: guard-never-prescribes-what-it-would-deny Disclosure-Reviewed: reviewed — public-safe only, private facet kept out of this commit
The projected distribution runs the Go tests outside any Git repository; owner-verb derivation resolves per-worktree sample paths, which needs a real Git directory. The tamper rendering check now uses the git-backed fixture like its sibling ownership test, instead of the package directory. Disclosure-Reviewed: reviewed — public-safe only, private facet kept out of this commit
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.
What
Builds on #226. A denial that only states the rule leaves a weaker model retrying the same blocked call. Every guard denial now carries the law's computed solution set: a capped
You can:list of exact runnable commands legal from the position the finding describes (owed human inputs marked<REQUIRED>), plus the full set on the opt-in structured payload (boatstackDenial.options, additive,schema_versionstays 1). Protected-path denials additionally name the path's declared owner verbs, derived at runtime from the state-ownership map.How
denial_solutions.go:enumerateDenialSolutions(repo, host, finding)by category family — phase findings re-enter the planning enumeration at the finding's own stage; operation findings get observation-only picks (observed-effect discipline);filesystem-destructionnames the sanctioned actuator (workspace-reap, confirmation owed); tamper findings get observers +diagnose-hook --host <host>andtamperOwnerVerbsfromStateRegistry()(a verb whose full arguments we cannot derive is named, never fabricated into a command). Reads only finding fields + declared tables: the deny path stays fast and cannot fail on unreadable state.denial.go:Denial.Options/OptionsTruncated/OwnerVerbs;You can:section in all three render modes between reassurance and hint;Structured()additiveoptions/owner_verbskeys;denialWithOptionscomposes the puredenialForwith the enumeration.safety.go: host deny contracts take the resolved repo; tamper findings carry the matched managed-path fragment asAttemptedPath(bounded, secret-free — same convention as phase-bypass).Boundary conformance
solution-set-derives-from-guard-declarations,guard-never-prescribes-what-it-would-deny; ownership derivation underevery-managed-path-has-a-declared-owner.denial_solutions_conformance_test.go— category totality sweep (every denial category enumerates or is a documented exception with reason), text-guard closure over every pick, phase-bypass picks re-checked against the interlock at the finding's stage, per-subtree ownership derivation pinned, rendering in all 3 modes + structured shape. Dual-reward corpus gains routine picks from real enumerations.malformed-tool-input,unsupported-host,unresolved-repository) render exactly as before; empty repo ⇒ repo-independent picks only.Verification
go build ./... && go vet ./...clean; fullgo test ./...green across all three packages.render-denial --demo --mode plain|ansi|markdownshows the new sections.2026-07-27-denials-name-their-solutions.md; preflight passes.