From 2ea7aff30504827c0c559147bc528394271b3571 Mon Sep 17 00:00:00 2001 From: os-zhuang Date: Thu, 25 Jun 2026 12:16:07 +0800 Subject: [PATCH] fix(examples): declare runAs:'system' on showcase webhook + approval-outcome flows (#1888) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit follow-up to #1888 (flow `runAs` enforcement). Two showcase flows relied on implicit elevation and would teach the wrong pattern as AI-authoring reference templates now that `runAs` is enforced: - `showcase_inbound_task_webhook` — an HMAC webhook (no authenticated user) that creates records. It only worked via the "no identity → security-skipped" fall-through. Now declares `runAs:'system'` — the correct, explicit pattern for external ingest (and robust once the target object carries RLS). - `showcase_invoice_signoff` — the revert-on-reject write is an approval-process outcome, not an act of the submitter. `runAs:'system'` so it lands regardless of whether the submitter still has edit rights on a "sent" invoice. No behavioral regression in the showcase itself (broad member perms — the full dogfood gate was already green); this hardens the templates so they stay correct under stricter RLS and as patterns AI copies. The rest of the example flows were audited and are correct as-is: `system` where they cross owners (lead qualification, renewal/stale sweeps, todo escalation/recurrence) and `user` where they act on the trigger user's own accessible record (lead convert, task reassign, resilient-sync self-update). Verified: `pnpm --filter @objectstack/example-showcase typecheck` + full dogfood gate (173 tests) green. Refs #1888 Co-Authored-By: Claude Opus 4.8 --- examples/app-showcase/src/flows/index.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/examples/app-showcase/src/flows/index.ts b/examples/app-showcase/src/flows/index.ts index 61b3f568ae..283440255a 100644 --- a/examples/app-showcase/src/flows/index.ts +++ b/examples/app-showcase/src/flows/index.ts @@ -883,6 +883,10 @@ export const InvoiceDualSignoffFlow = defineFlow({ label: 'Invoice Dual Sign-off (parallel approval)', description: 'On send, requires finance AND legal to both approve via one aggregating approval node — demonstrates parallel approvals without a token tree (ADR-0039 Track A).', type: 'autolaunched', + // The revert-on-reject write is an approval-process outcome, not an act of the + // submitter — run it as the system principal so it lands regardless of whether + // the submitter still has edit rights on a "sent" invoice (#1888 runAs enforced). + runAs: 'system', nodes: [ { id: 'start', @@ -1160,6 +1164,11 @@ export const InboundTaskWebhookFlow = defineFlow({ label: 'Inbound Task Webhook', description: 'Creates a task from an external system via the HMAC-verified inbound hook.', type: 'api', + // An inbound webhook has no authenticated user, so the create must run as the + // system principal (#1888 runAs is now enforced). Without this it relies on the + // "no identity → security-skipped" fall-through, which breaks the moment the + // target object carries row-level security. + runAs: 'system', nodes: [ { id: 'start',