Skip to content

feat(showcase): stamp real submitters on the seeded approval requests - #3411

Merged
os-zhuang merged 1 commit into
mainfrom
feat/showcase-approval-submitters
Jul 22, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
feat/showcase-approval-submitters

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

The gap

Every seeded approval request had a null submitter: the demo launches flows as SYS, and the approval node stamps the requester from the flow context (submitterId: context?.userId ?? null). Three consequences, all invisible until you look:

  • 申请人 rendered as — on every row and in the drawer;
  • the 我发起的 inbox tab was empty for every user — the whole surface was dead;
  • the submitter-only affordances never rendered anywhere. Send reminder and Recall are gated on submitter_id == ctx.user.id, so with no submitter only 5 of the 7 declared approval actions could ever appear.

The fix

Pass userId on the engine context, routed deliberately:

Request Submitter Why
Invoice Dual Sign-off admin the logged-in dev admin owns one request → 我发起的 is non-empty and the submitter-only actions have somewhere to render
Committee Quorum · Expense Sign-off Mei Phone (demo) holds no approval position → a clean requester who is never one of her own approvers

Verified in the running console

申请人 now shows real names; 我发起的 holds the admin's request; and the same viewer sees

  • on Mei's requests → Approve Reject Reassign Send back Request info
  • on his own request → those plus Send reminder and Recall

so all 7 declared actions render, gated by the viewer's role. tsc --noEmit ✓, boot log 0 ERROR.

Follow-up to #3409, from the #3358 §1 evidence pass. Private example → no changeset.

🤖 Generated with Claude Code

Every seeded request had a null submitter, because the demo launches flows as
SYS and the approval node stamps the requester from the flow context
(`submitterId: context?.userId ?? null`, approval-node.ts). Consequences, all
invisible until you go looking:

  * 申请人 rendered as `—` on every row and in the drawer;
  * the "我发起的" inbox tab was empty for every user, so that whole surface
    was dead; and
  * the submitter-only affordances never rendered anywhere — `Send reminder`
    and `Recall` are gated on `submitter_id == ctx.user.id`, so with no
    submitter they were unreachable and only 5 of the 7 declared approval
    actions could ever appear.

Pass `userId` on the engine context and route it deliberately:

  * Invoice Dual Sign-off → submitted by the **admin**, so the logged-in dev
    admin owns one request and "我发起的" is non-empty with the submitter-only
    actions visible;
  * Committee Quorum and Expense Sign-off → submitted by **Mei Phone (demo)**,
    who holds no approval position and is therefore a clean requester who is
    never one of her own approvers.

Verified in the running console: 申请人 now shows real names, "我发起的" holds
the admin's request, and the same viewer sees `Approve / Reject / Reassign /
Send back / Request info` on Mei's requests but those **plus** `Send reminder`
and `Recall` on his own — i.e. all 7 declared actions render, gated by role.
`tsc --noEmit` passes; boot log stays at 0 ERROR.

From the #3358 §1 evidence pass; follow-up to #3409.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@os-zhuang os-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Jul 22, 2026
@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Building Building Preview, Comment Jul 22, 2026 8:25pm

Request Review

@os-zhuang
os-zhuang merged commit 8ee8c52 into main Jul 22, 2026
17 of 18 checks passed
@os-zhuang
os-zhuang deleted the feat/showcase-approval-submitters branch July 22, 2026 20:31
os-project-manager pushed a commit that referenced this pull request Aug 18, 2026
…ot run without (#9308)

Three platform capabilities had no fixture anywhere in the reference app, so the
checklist items covering them were not failing — they were unrunnable.

1. A second, actually LOGINABLE member. The demo personas have been sys_user rows
   since #3409/#3411 and neither could sign in, blocking every item that needs two
   acting identities. The non-obvious half: better-auth 1.7 keys accounts on
   (issuer, providerAccountId), so a credential row carrying any other issuer is
   invisible to sign-in and fails INVALID_EMAIL_OR_PASSWORD behind a "User not
   found" warn that points at the user row instead of the account.
   seed-approval-demo.ts now provisions the credential account through better-auth's
   own $context and READS the issuer off the dev admin's own credential row rather
   than re-spelling a constant plugin-auth owns. Dev-only by construction.

2. showcase_client_brief — the first object to opt into publicSharing. Carries
   redactFields, an expiry cap and an eligibility predicate; the seed holds a
   published brief (mint-eligible) and a draft one (refused RECORD_NOT_ELIGIBLE) so
   the predicate is falsifiable. Every other object still declines the opt-in, which
   is what keeps the per-object 422 a real control.

3. showcase_client_liaison — the app's first readable:false FLS grant, on the same
   three showcase_project budget figures showcase_contributor governs with
   readable:true/editable:false. All three move together because budget_remaining is
   a formula over budget - spent.

Downstream reconciliations, each deliberate and none a re-baseline: access-matrix.json
gains two rows and moves none; the persona x CRUD census follows the matrix
(50/50 -> 54/54, arithmetic recorded at the assertion) and its fixture maps learn the
new object; the position count pin follows the new position. Five checklist items are
revised in the same change — gap text kept, marked closed-by-fixture, revision bumped,
history appended (#7670 pattern).

Not landed: the writable-package summary field for automation.rollup-summary-filter.
It is not seed data and cannot be made into seed data — see FOLLOW-UPS.md section 6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 23, 2026
…ot run without (objectstack-ai#9308) (objectstack-ai#9789)

* test(showcase): land the seed fixtures the platform checklist could not run without (objectstack-ai#9308)

Three platform capabilities had no fixture anywhere in the reference app, so the
checklist items covering them were not failing — they were unrunnable.

1. A second, actually LOGINABLE member. The demo personas have been sys_user rows
   since objectstack-ai#3409/objectstack-ai#3411 and neither could sign in, blocking every item that needs two
   acting identities. The non-obvious half: better-auth 1.7 keys accounts on
   (issuer, providerAccountId), so a credential row carrying any other issuer is
   invisible to sign-in and fails INVALID_EMAIL_OR_PASSWORD behind a "User not
   found" warn that points at the user row instead of the account.
   seed-approval-demo.ts now provisions the credential account through better-auth's
   own $context and READS the issuer off the dev admin's own credential row rather
   than re-spelling a constant plugin-auth owns. Dev-only by construction.

2. showcase_client_brief — the first object to opt into publicSharing. Carries
   redactFields, an expiry cap and an eligibility predicate; the seed holds a
   published brief (mint-eligible) and a draft one (refused RECORD_NOT_ELIGIBLE) so
   the predicate is falsifiable. Every other object still declines the opt-in, which
   is what keeps the per-object 422 a real control.

3. showcase_client_liaison — the app's first readable:false FLS grant, on the same
   three showcase_project budget figures showcase_contributor governs with
   readable:true/editable:false. All three move together because budget_remaining is
   a formula over budget - spent.

Downstream reconciliations, each deliberate and none a re-baseline: access-matrix.json
gains two rows and moves none; the persona x CRUD census follows the matrix
(50/50 -> 54/54, arithmetic recorded at the assertion) and its fixture maps learn the
new object; the position count pin follows the new position. Five checklist items are
revised in the same change — gap text kept, marked closed-by-fixture, revision bumped,
history appended (objectstack-ai#7670 pattern).

Not landed: the writable-package summary field for automation.rollup-summary-filter.
It is not seed data and cannot be made into seed data — see FOLLOW-UPS.md section 6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza

* i18n(showcase): translate the client-brief fixture into zh-CN (objectstack-ai#9308)

`check:i18n-coverage` went red on PR objectstack-ai#9789: untranslated declared strings for
examples/app-showcase grew 393 -> 405. The showcase declares supportedLocales
['en', 'zh-CN'], so every label the new `showcase_client_brief` object declares
owed a zh-CN rendering. Measured: all 12 are that object — label, pluralLabel,
description, seven field labels and both `status` options. The new position and
permission set are NOT counted by this rule, which counts object/field/option
strings only.

Both locales are written key for key. `check-i18n-coverage` is a two-sided
ratchet, so an `en` entry landing without its zh-CN mirror is what widens the
gap rather than closing it.

Renderings follow the ones this bundle already established rather than being
coined fresh — 标题 / 项目 / 状态 / 备注 / 负责人 are the words showcase_task and
showcase_invoice already use for the same concepts, which is the whole point of
the file: a list must not mix a translated 状态 with an English Owner beside it.
草稿 / 已发布 are new (nothing here rendered a draft state before); 已发布 follows
the 已完成 pattern the bundle uses for a state that has been reached.

The description is mirrored byte-for-byte from the object declaration on the en
side and translated in full on the zh side, ADR reference included. A bundle
entry overrides the schema label for its locale, so a "tidied" English
description would silently rewrite product copy under the guise of translating
it, and a half-dropped Chinese one is the locale degradation the bundle exists
to prevent.

Count back to 393 exactly, not merely stopped growing. The baseline in
scripts/i18n-coverage-baseline.json is untouched (no --update anywhere).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza

---------

Co-authored-by: Claude <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — f34b6b44 Deployed Jul 22, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant