Skip to content

settings: a cluster says when it wants to be asked - #42

Merged
github-actions[bot] merged 1 commit into
developfrom
feat/act-but-ask-me-first
Sep 12, 2026
Merged

github-actions[bot] merged 1 commit into
developfrom
feat/act-but-ask-me-first

Conversation

@traviswu-bigstack

Copy link
Copy Markdown
Contributor

What

ADR 0011's consent dial on the executor: a consent file beside action-level (never / destructive / always), a Registry.Consent() observable, Approved on the channel-open frame, and a refusal for a call this cluster required a person for and did not get.

Why

The level moved what may happen; nothing moved who must agree. ActionApprove decides who may approve, never whether approval is required — that was a fixed set in the SaaS's needsApproval. So "may restart a service, but always ask" was inexpressible, as was its opposite.

Unset means always, which is exactly what the gate already did, so no deployed cluster changes. No cluster has a consent file today, so the new refusal cannot break a working setup.

Reviewer notes

Why the executor enforces at all. It cannot see the human — only the SaaS can ask one. But without a refusal here, a value on the node would change nothing: a SaaS with a stale mirror would skip the question and the cluster would never know. With it, that becomes an audited refusal. It does not survive a SaaS that liesapproved comes over the tunnel — and that is stated in the field's own comment; the action level is the control that does, because this side answers it from its own state.

The file's mode rule is the level's, for a sharper reason: whoever can write it can stop the cluster asking a person before it acts. Readability is unconstrained — a policy statement its customer should read without root.

destructive asks for nothing today. No allowlist entry sets it. DestructiveToolsExist() is computed rather than written down, so the startup caveat disappears on its own when someone adds a destroying tool instead of becoming the next stale comment.

A break that did not fail, and the test that now catches it. Hardcoding true where serveTool reads ch.Open.Approved passed the entire suite — every consent test called the registry directly, so none crossed the tunnel. TestApprovalOnTheOpenFrameReachesTheRegistry runs the real session in both directions; the same break fails it.

Two other breaks, each watched then restored: flipping DefaultConsent to never failed four tests including the ADR 0016 layer-2 wiring test; disabling refusedWithoutAPerson failed the refusal test.

Call gained an approved parameter — the churn in the existing tests is that and nothing else.

Docs

bigstack-handbook#639.

ADR 0011's second dial, accepted 2026-09-10 and until now only on paper. The
level moved what may happen; nothing moved who must agree, so the configuration
the ADR itself named as the one customers most want — "may restart a service,
but always ask" — was inexpressible, as was its opposite.

consent is a per-cluster file beside the action level: never, destructive,
always. Unset means always, which is exactly today's behaviour, so no deployed
cluster changes. A file others may write is refused for a sharper reason than
the level's — whoever can write it can stop the cluster asking a person before
it acts.

The two dials are enforced at different points because they answer different
questions. The executor can answer "does this cluster serve this class?" from
its own state and trusts the SaaS for nothing. It cannot observe whether a
person was asked, so the SaaS does the asking and reports it on the channel-open
frame, and the executor refuses a call its own setting required a person for and
did not get. That catches the honest failure — a stale mirror, a bug, an
operator who changed the file a minute ago — and turns it into an audited
refusal instead of an unattended call. It does not catch a SaaS that lies; the
action level is the control that survives that, and the claim is audited so a
customer sees an approval asserted for a call their own record never saw.

Destructive is a declared property of a tool, not a fourth impact class. No tool
sets it: creates create, reads read, probes clean up after themselves. So
destructive currently asks for nothing, which the startup line says out loud —
computed from the allowlist, so the caveat disappears by itself when someone
adds one rather than becoming the next comment that outlived its subject.

needsApproval's comment said approval was required "every time" and pointed at
the ladder's later rungs as where it would become configurable. They are here;
the comment is rewritten rather than left standing.

Signed-off-by: Travis Wu <travis.wu@bigstack.co>
@traviswu-bigstack
traviswu-bigstack marked this pull request as ready for review September 12, 2026 07:50
@traviswu-bigstack traviswu-bigstack added the done Apply to a PR to trigger the fast-forward merge label Sep 12, 2026
@github-actions
github-actions Bot merged commit f1b1f21 into develop Sep 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

done Apply to a PR to trigger the fast-forward merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant