Skip to content

test(server): [YW-269] cover real createDashframeServer vault seam — anti-shadow + injection#133

Open
youhaowei wants to merge 1 commit into
mainfrom
charixandra/yw-269-test-the-real-createdashframeserver-vault-seam-anti-shadow
Open

test(server): [YW-269] cover real createDashframeServer vault seam — anti-shadow + injection#133
youhaowei wants to merge 1 commit into
mainfrom
charixandra/yw-269-test-the-real-createdashframeserver-vault-seam-anti-shadow

Conversation

@youhaowei

@youhaowei youhaowei commented Jun 17, 2026

Copy link
Copy Markdown
Owner

What this PR does

Adds direct unit tests for the security-critical vault-injection seam in createDashframeServer (apps/server/src/app.ts). The prior state had no test driving the real production code — vault-control-plane.test.ts had re-implemented an equivalent wrapper inline, meaning a merge-order regression in app.ts would go undetected.

Test cases (in apps/server/src/app.test.ts)

  1. Anti-shadow — injected vault wins over a bogus vault key in call context. Injects a real vault (with connector-key class registered), then passes a bogus vault (no connector-key backend) in the request context. The bogus vault causes a registry throw if it wins; the test succeeds only when the injected vault is used. A reversed merge order would fail this test with a "no default backend for class connector-key" error.

  2. Anti-shadow — identity verification via hasCallCount. Stronger form: after addDataSource + getDataSource with a bogus vault in context, asserts realBackend.hasCallCount > 0 — confirming the injected backend's has() path was exercised on the read side.

  3. No-injection short-circuit. buildDashframeApp({ db }) with no vault or onWrite returns the raw unwrapped app. A CSV source (no credential) round-trips without needing a vault.

  4. Vault visible to handlers via app.call. Credential-bearing addDataSource succeeds only when the injected vault is in context. The buildDashframeApp wrapper applies the same { ...(context ?? {}), ...staticContext } merge to both call and runHandler.

app.ts change — classification: pure refactor-for-testability (KEPT)

The prior agent extracted the vault-injection/anti-shadow merge logic from createDashframeServer into a new exported buildDashframeApp function, then replaced the inline code with a call to buildDashframeApp. The logic is byte-for-byte identical — same staticContext construction, same hasStaticContext flag, same spread order { ...(context ?? {}), ...staticContext }, same onWrite null-check, same short-circuit when vault == null && onWrite == null. No behavior change. This is the standard "hoist-to-export for testability" pattern (see assertBindAuthorized in the same file).

No production behavior was changed. The tests exercise the real seam — a regression in app.ts would fail them.

Local gate

  • turbo typecheck (whole graph): PASS — 44 tasks, 26 cached, 0 errors
  • lint: PASS — clean (pre-existing @dashframe/ui warning unrelated to this diff)
  • format: PASS — no violations
  • bun test (apps/server): 197/197 PASS — including all 4 new seam tests
  • code-review --effort high (clawpatch): 0 MUSTs — 2 comment-accuracy SUGGESTs resolved inline before commit

Tracked internally as YW-269

🤖 Generated with Claude Code

Greptile Summary

This PR extracts the vault-injection and onWrite wiring from the inlined body of createDashframeServer into a separately-exported buildDashframeApp function, then adds four direct unit tests that drive the real seam. The production logic is byte-for-byte identical to what was removed — same merge order, same short-circuit, same onWrite swallow-on-throw.

  • app.ts: buildDashframeApp is exported with the same vault anti-shadow merge ({ ...(context ?? {}), ...staticContext }) and the identical onWrite null-check; createDashframeServer now delegates to it.
  • app.test.ts: Four new tests verify the anti-shadow invariant (two forms: success path and backend call-count identity), the no-injection short-circuit, and vault visibility via app.call.

Confidence Score: 4/5

Safe to merge — the production refactor is a mechanical extraction with no behavior change, and the new tests tighten coverage of the security-critical vault seam.

The vault anti-shadow logic is unchanged and the tests confirm correct merge order. The only open items are documentation gaps: the AC3 section banner names runHandler but the test only exercises app.call, and the new exported buildDashframeApp JSDoc doesn't note that onWrite is silent for writes going through runHandler.

The buildDashframeApp JSDoc in app.ts (lines 220–234) and the AC3 section banner in app.test.ts (line 596–598) are the two spots worth a quick pass before merge.

Important Files Changed

Filename Overview
apps/server/src/app.ts Pure refactor: vault-injection logic extracted verbatim into exported buildDashframeApp; createDashframeServer delegates to it. Logic and merge-order are byte-for-byte identical. Minor doc gap: onWriterunHandler asymmetry not mentioned in the new JSDoc.
apps/server/src/app.test.ts Adds four focused seam tests covering anti-shadow (two forms), no-injection short-circuit, and vault threading via app.call. AC3 section banner says "runHandler" but the test only exercises app.call; acknowledged in-body but the label is misleading.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Caller
    participant buildDashframeApp
    participant WrappedApp
    participant rawApp

    Caller->>buildDashframeApp: "buildDashframeApp({ db, vault?, onWrite? })"
    buildDashframeApp->>rawApp: "createWyStack({ db, functions })"
    rawApp-->>buildDashframeApp: rawApp

    alt "vault == null && onWrite == null"
        buildDashframeApp-->>Caller: rawApp (short-circuit)
    else vault or onWrite supplied
        buildDashframeApp-->>Caller: "WrappedApp { ...rawApp, call, runHandler }"
    end

    Caller->>WrappedApp: call(path, args, context?)
    WrappedApp->>WrappedApp: "merged = { ...(context ?? {}), ...staticContext }"
    Note over WrappedApp: staticContext (vault) wins — cannot be shadowed
    WrappedApp->>rawApp: call(path, args, merged)
    rawApp-->>WrappedApp: result
    opt "onWrite != null && tablesWritten.size > 0"
        WrappedApp->>WrappedApp: onWrite() [errors swallowed]
    end
    WrappedApp-->>Caller: result

    Caller->>WrappedApp: runHandler(path, args, tracked, context?)
    WrappedApp->>WrappedApp: "merged = { ...(context ?? {}), ...staticContext }"
    WrappedApp->>rawApp: runHandler(path, args, tracked, merged)
    rawApp-->>WrappedApp: result
    Note over WrappedApp: onWrite NOT fired from runHandler path
    WrappedApp-->>Caller: result
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Caller
    participant buildDashframeApp
    participant WrappedApp
    participant rawApp

    Caller->>buildDashframeApp: "buildDashframeApp({ db, vault?, onWrite? })"
    buildDashframeApp->>rawApp: "createWyStack({ db, functions })"
    rawApp-->>buildDashframeApp: rawApp

    alt "vault == null && onWrite == null"
        buildDashframeApp-->>Caller: rawApp (short-circuit)
    else vault or onWrite supplied
        buildDashframeApp-->>Caller: "WrappedApp { ...rawApp, call, runHandler }"
    end

    Caller->>WrappedApp: call(path, args, context?)
    WrappedApp->>WrappedApp: "merged = { ...(context ?? {}), ...staticContext }"
    Note over WrappedApp: staticContext (vault) wins — cannot be shadowed
    WrappedApp->>rawApp: call(path, args, merged)
    rawApp-->>WrappedApp: result
    opt "onWrite != null && tablesWritten.size > 0"
        WrappedApp->>WrappedApp: onWrite() [errors swallowed]
    end
    WrappedApp-->>Caller: result

    Caller->>WrappedApp: runHandler(path, args, tracked, context?)
    WrappedApp->>WrappedApp: "merged = { ...(context ?? {}), ...staticContext }"
    WrappedApp->>rawApp: runHandler(path, args, tracked, merged)
    rawApp-->>WrappedApp: result
    Note over WrappedApp: onWrite NOT fired from runHandler path
    WrappedApp-->>Caller: result
Loading

Fix All in Claude Code Fix All in Codex Fix All in Cursor

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/server/src/app.test.ts:596-598
**Section banner doesn't match the test it guards**

The comment at the section boundary labels this block "AC3 — runHandler threads the injected vault", but the single test inside only exercises `app.call``runHandler` is never invoked. A developer who later wants to add a direct `runHandler` test might skip this section assuming it's already covered, or write a duplicate. The body comment explains the limitation correctly, but the banner label should reflect what's actually proven (e.g. "AC3 — vault threads into handlers via `app.call`") so the gap between intended and exercised coverage is unambiguous at a glance.

### Issue 2 of 2
apps/server/src/app.ts:220-234
**`onWrite``runHandler` asymmetry is undocumented on the now-public API**

`buildDashframeApp` is newly exported, so its JSDoc is now the authoritative contract. The `onWrite` hook fires only from the `call` wrapper; the `runHandler` wrapper applies the vault-merge but does not call `onWrite` (preserved from the original inlined code). A caller supplying `onWrite` to `buildDashframeApp` directly — rather than via `createDashframeServer` — will see silent non-firing for writes that go through `runHandler`. Worth a one-line note such as "`onWrite` fires only from `call`; `runHandler` performs context injection but does not invoke the hook".

Reviews (1): Last reviewed commit: "test(server): cover real buildDashframeA..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

… injection

Extract buildDashframeApp from createDashframeServer so the vault-injection
seam (the anti-shadow merge and the short-circuit path) can be driven by direct
unit tests without a live socket. Three contracts exercised:

1. Anti-shadow — injected vault wins over a bogus vault key in request context;
   a merge-order regression would cause the test to fail with a registry error.
2. No-injection short-circuit — buildDashframeApp({db}) returns the raw unwrapped
   app when vault and onWrite are both absent.
3. Vault visible to handlers — credential-bearing addDataSource succeeds only
   when the injected vault is in context.

The app.ts change is a pure refactor-for-testability: the merge logic (the
anti-shadow { ...(context ?? {}), ...staticContext } spread and the onWrite hook
wiring) is extracted verbatim into buildDashframeApp; createDashframeServer now
delegates to it. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@linear-code

linear-code Bot commented Jun 17, 2026

Copy link
Copy Markdown

YW-269

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@youhaowei, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 53 minutes and 9 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 62298f87-e692-47da-a22b-b57d00085367

📥 Commits

Reviewing files that changed from the base of the PR and between 422f5d7 and 4bb8726.

📒 Files selected for processing (2)
  • apps/server/src/app.test.ts
  • apps/server/src/app.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment on lines +596 to +598
// ---------------------------------------------------------------------------
// AC3 — runHandler threads the injected vault
// ---------------------------------------------------------------------------

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Section banner doesn't match the test it guards

The comment at the section boundary labels this block "AC3 — runHandler threads the injected vault", but the single test inside only exercises app.callrunHandler is never invoked. A developer who later wants to add a direct runHandler test might skip this section assuming it's already covered, or write a duplicate. The body comment explains the limitation correctly, but the banner label should reflect what's actually proven (e.g. "AC3 — vault threads into handlers via app.call") so the gap between intended and exercised coverage is unambiguous at a glance.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/server/src/app.test.ts
Line: 596-598

Comment:
**Section banner doesn't match the test it guards**

The comment at the section boundary labels this block "AC3 — runHandler threads the injected vault", but the single test inside only exercises `app.call``runHandler` is never invoked. A developer who later wants to add a direct `runHandler` test might skip this section assuming it's already covered, or write a duplicate. The body comment explains the limitation correctly, but the banner label should reflect what's actually proven (e.g. "AC3 — vault threads into handlers via `app.call`") so the gap between intended and exercised coverage is unambiguous at a glance.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex Fix in Cursor

Comment thread apps/server/src/app.ts
Comment on lines +220 to +234
/**
* Build the WyStack app with vault injection and onWrite hook wiring — without
* starting an HTTP server.
*
* Extracted from `createDashframeServer` so the vault-injection seam (the
* anti-shadow merge and the short-circuit path) can be driven by direct unit
* tests without a live socket. `createDashframeServer` calls this internally;
* tests import and exercise it directly.
*
* Security invariant: `vault` is injected into every handler context via a
* static spread that wins over per-call context. The merge order
* `{ ...(context ?? {}), ...staticContext }` means the vault key cannot be
* shadowed by a caller-supplied context — the vault identity is fixed for the
* lifetime of the returned app.
*/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 onWriterunHandler asymmetry is undocumented on the now-public API

buildDashframeApp is newly exported, so its JSDoc is now the authoritative contract. The onWrite hook fires only from the call wrapper; the runHandler wrapper applies the vault-merge but does not call onWrite (preserved from the original inlined code). A caller supplying onWrite to buildDashframeApp directly — rather than via createDashframeServer — will see silent non-firing for writes that go through runHandler. Worth a one-line note such as "onWrite fires only from call; runHandler performs context injection but does not invoke the hook".

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/server/src/app.ts
Line: 220-234

Comment:
**`onWrite``runHandler` asymmetry is undocumented on the now-public API**

`buildDashframeApp` is newly exported, so its JSDoc is now the authoritative contract. The `onWrite` hook fires only from the `call` wrapper; the `runHandler` wrapper applies the vault-merge but does not call `onWrite` (preserved from the original inlined code). A caller supplying `onWrite` to `buildDashframeApp` directly — rather than via `createDashframeServer` — will see silent non-firing for writes that go through `runHandler`. Worth a one-line note such as "`onWrite` fires only from `call`; `runHandler` performs context injection but does not invoke the hook".

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex Fix in Cursor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant