docs(approval-gate): mark role/quorum fields as advisory; runtime warns when set#34
Merged
gitsad merged 2 commits intoMay 28, 2026
Conversation
…ns when set
The approval-gate schema accepts `allowedRoles`, `requiredApprovers`, and
`requireReason`, and the documentation across `docs/reference/component-catalog.md`,
`docs/guides/creating-blueprints.md`, `docs/guides/creating-documents.md`, and
`docs/guides/enterprise-features.md` previously described these as enforced
controls ("Supports role-based access control", "Approval gate enforces
manager/director role", etc.). `SECURITY.md` classified approval-gate bypass
as a reportable vulnerability. The runtime does not, in fact, verify approver
identity, enforce role membership, or require multiple distinct approvers:
- `packages/renderer-react/src/components/ApprovalGateRenderer.tsx:30-49`
dispatches `APPROVAL_GRANTED` with a hardcoded `actor: { id: 'current-user' }`
straight to `store.dispatch`, bypassing `approvalGateHandler.onAction`. The
handler's `ctx.policy.enforce('approval_grant')` is unreachable from the UI.
- `packages/runtime/src/core/document-store.ts:194-201` handles `APPROVAL_GRANTED`
by setting `status: 'approved'` without invoking `policy.enforce` and without
consulting `allowedRoles` / `requiredApprovers` / `requireReason`.
- `createDocumentStore` accepts `options.registry?: AttachableRegistry` but the
function body never reads it.
This change aligns documentation, schema descriptions, and `SECURITY.md` with
shipped behaviour, and adds a runtime warning so embedders are signalled when
they declare advisory fields. No behaviour change beyond the new warning.
Schema (`packages/spec/src/schemas/components/approval-gate.ts`)
- `.describe()` text on `requiredApprovers`, `allowedRoles`, `requireReason`
marks them advisory and points at the security-model callout. The describe
for `requiredApprovers` makes explicit that the default value of `1` is
equally advisory at the runtime layer.
Runtime (`packages/runtime/src/core/document-store.ts`)
- `warnIfAdvisoryApprovalFields(comp)` helper, called once per gate per store
instance from the init loop and `updateAst`'s new-component branch.
- The warning lists which advisory fields the gate declares and states
explicitly that ALL approval-gate role/quorum/reason fields are advisory at
the runtime layer regardless of which one triggered the warning.
- JSDoc acknowledges the `console.warn` delivery limitation (suppressed in
`vitest --silent`, SSR, structured-logger pipelines). A structured-logger
integration is the natural follow-up but is intentionally deferred because
`packages/runtime` takes no logger dependency today.
Tests (`packages/runtime/tests/document-store.test.ts`)
- Four new tests via `vi.spyOn(console, 'warn')` covering:
(a) init with `allowedRoles` warns once,
(b) `requiredApprovers > 1` + `requireReason: true` warns with both fields listed,
(c) bare gate does NOT warn (regression guard),
(d) gate added later via `updateAst` warns when it enters the store.
Docs
- `component-catalog.md`: dropped "Supports role-based access control" from
the approval-gate opener. Added an "Important — security model" callout
above the property table spelling out the trust boundary. Field table marks
the three fields advisory. Rewrote the example description to drop the
"SOX compliance requirement" framing.
- `creating-blueprints.md`: two "Approval gate enforces … role" checklist
items changed to "declares … (host application enforces)".
- `creating-documents.md`: advisory callouts after both approval-gate examples.
- `enterprise-features.md`: the "Putting It All Together" code comment notes
that runtime policy enforcement applies to `webhook_call` only and points
at the approval-gate security model.
Security policy (`SECURITY.md`)
- Reworded the approval-gate-bypass bullet so it no longer classifies
advisory-by-design behaviour as a reportable vulnerability. Reports that
hinge on runtime-layer enforcement of `allowedRoles` / `requiredApprovers`
/ `requireReason` are closed not-a-bug.
Out of scope (intentionally — happy to follow up)
- Wiring `approval-gate-handler` into `document-store`'s dispatch path.
Requires deciding how the host injects a verified actor identity
(proposed shape: `MdmaProvider currentUser={{id, role}}` flowing into
`AttachableContext`).
- Per-blueprint compliance claims in `blueprints/change-management`,
`blueprints/clinical-ops`, `blueprints/kyc-case`, `blueprints/incident-triage`,
`blueprints/customer-escalation`. Their manifests/READMEs name the
approval-gate as the implementation of specific controls (SOX 404, clinical-
staff restriction, etc.). Those edits warrant the maintainers' judgement.
- Prompt-pack author guidance (`packages/prompt-pack/src/prompts/mdma-author/_shared.ts`)
telling the LLM these fields restrict approvers. Should track whatever the
enforcement-wiring decision above decides.
- Refactoring advisory-fields checks into a `walkComponents(ast, visitor)`
utility once more component types carry advisory metadata.
4442c1d to
69e71ff
Compare
Member
|
@aziolek thanks for raising this. Yes, definitely you are right. MDMA's role is not to verify any app data that can be handled via approval-gate but for sure it should be passed through to be able to be handled by other functions. Looking forward to next PR or just let me know then I will provide necessary fixes related to this element. |
gitsad
approved these changes
May 27, 2026
Member
|
changed target branch to fix/approval-gates |
Biome lint/style/useTemplate flagged the multi-line string concatenation; collapse to a single template literal to satisfy CI.
Author
Should pass now. |
gitsad
approved these changes
May 28, 2026
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.
Summary
Aligns the approval-gate documentation and schema descriptions with what the runtime actually enforces. Adds a
console.warn(once per gate at store init) when a document declaresallowedRoles,requiredApprovers > 1, orrequireReason: true, pointing embedders at the security-model section of the component catalog.No behaviour change beyond the new warning.
Motivation
The current docs describe the approval-gate's
allowedRoles/requiredApprovers/requireReasonfields as enforced controls. Reading the runtime, they are not:packages/renderer-react/src/components/ApprovalGateRenderer.tsx:30-49dispatchesAPPROVAL_GRANTEDwith a hardcodedactor: { id: 'current-user' }straight tostore.dispatch, bypassingapprovalGateHandler.onAction. The handler'sctx.policy.enforce('approval_grant')is therefore unreachable from the UI.packages/runtime/src/core/document-store.ts:194-201handlesAPPROVAL_GRANTEDby settingstatus: 'approved'and writing the actor into bindings, without invokingpolicy.enforce, without consultingallowedRoles/requiredApprovers/requireReason, and without validating the actor.packages/runtime/src/core/document-store.ts:20acceptsoptions.registry?: AttachableRegistrybut the function body never reads it (compareoptions.sessionId,options.documentId,options.environment,options.policy, all of which are read).ButtonRenderer,FormRenderer, andWebhookRendererdo not readcomponentState.disabled/componentState.visible, so even adisabled: "{{gate.status}}"binding wouldn't prevent a downstream action from firing.packages/runtime/tests/document-store.test.ts:150codifies the current behaviour as the desired contract — a directstore.dispatch({ type: 'APPROVAL_GRANTED', componentId: 'gate1', actor: { id: 'user-1', role: 'manager' } })flipsstatusto'approved'against a gate that has no role/quorum constraints, with no assertion that any policy check ran.There are two coherent ways to address this. This PR is the smaller of the two — make the docs honest. The larger alternative is to add a host-identity API and wire
approvalGateHandler.onActioninto dispatch; that requires an API surface decision (where verified identity comes from) that this PR doesn't preempt.Changes
packages/spec/src/schemas/components/approval-gate.ts— add.describe()text onrequiredApprovers,allowedRoles,requireReasonmarking them advisory.packages/runtime/src/core/document-store.ts— addwarnIfAdvisoryApprovalFields(comp)helper, called from the init loop and fromupdateAst's new-component branch. Emitsconsole.warnonce per gate the first time it enters the store.docs/reference/component-catalog.md— drop "Supports role-based access control" from the section opener; add an "Important — security model" callout above the property table explaining the trust boundary; update the field table to mark the three fields advisory; rewrite the example description to drop the "SOX compliance requirement" framing.docs/guides/creating-blueprints.md— change two "Approval gate enforces … role" checklist items to "declares" with a note that the host application enforces.docs/guides/creating-documents.md— add advisory callouts after the two approval-gate examples (themanager-gatevisibility example and thecompliance-approvalexample).docs/guides/enterprise-features.md— annotate the "Putting It All Together" example: the// 3. Create store with policy enforcementcomment now notes that policy enforcement currently applies to the webhook (webhook_call) only, and points at the approval-gate security model.Out of scope (happy to follow up)
approval-gate-handlerintodocument-store's dispatch path. Requires deciding how the host injects a verified actor identity (proposed shape:MdmaProvider currentUser={{id, role}}flowing intoAttachableContext).blueprints/change-management,blueprints/clinical-ops,blueprints/kyc-case,blueprints/incident-triage,blueprints/customer-escalation. Their manifests/READMEs name the approval-gate as the implementation of specific controls (SOX 404, clinical-staff restriction, etc.). Those edits probably warrant the maintainers' judgement.SECURITY.md's in-scope list, which currently classifies approval-gate bypass as a reportable vulnerability — that classification only fits a runtime that ships enforcement.packages/prompt-pack/src/prompts/mdma-author/_shared.ts) that tells the LLM these fields restrict approvers. Should track whatever (1) decides.Disclosure note
SECURITY.mdasks for private disclosure of approval-gate bypass viaopen-source@mobilereality.pl. This PR addresses only the documentation/schema-description side and intentionally does not disclose anything not already readable in the public source. If you'd prefer to handle the conversation about (1) — wiring real enforcement — privately first, I'm happy to email and treat (2)/(3)/(4) as follow-ups gated on that decision.