Skip to content

Commit ccdb03d

Browse files
authored
v0.7.67: provenance ffs, files fixes, library post
2 parents f4cc8d7 + bc8826a commit ccdb03d

60 files changed

Lines changed: 1349 additions & 222 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/content/docs/en/platform/self-hosting/environment-variables.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ See [Observability](/platform/self-hosting/observability).
181181
| `NEXT_PUBLIC_CHAT_DISABLED` | Set to `true` to hide the Chat module: the workspace lands on your first workflow, with no chats list, scheduled tasks, or editor Chat panel. Chat is shown when unset; `bun run setup` sets it for you if you skip the chat key |
182182
| `PII_REDACTION` | Redact PII from workflow logs via Data Retention rules; requires the PII service and a cluster-reachable `INTERNAL_API_BASE_URL` |
183183
| `PII_GRANULAR_REDACTION` | Additionally expose the execution-altering redaction stages |
184+
| `DURABLE_SECRET_PROVENANCE_ENFORCED_SURFACES` | Durable stores where a value whose secret provenance was never recorded fails the run instead of logging a warning. `all`, or a comma-separated subset of `memory`, `table-row`, `knowledge`. Unset (nothing enforced) by default |
184185
| `ADMIN_API_KEY` | Admin API key for GitOps operations and organization provisioning |
185186

186187
## Enterprise Features

apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/constants.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { NavMenu } from '@/app/(landing)/components/navbar/components/nav-menu-chip/types'
22

33
/**
4-
* The Platform menu - Sim's modules. Six items in a three-column grid. Each
5-
* description names the outcome the module unlocks for your agents.
4+
* The Platform menu - Sim's modules. Five items in a three-column grid, so the
5+
* bottom-right cell is empty. Each description names the outcome the module
6+
* unlocks for your agents.
67
*/
78
export const PLATFORM_MENU: NavMenu = {
89
label: 'Platform',

apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/nav-menu-chip.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,9 @@ import type { NavMenu } from '@/app/(landing)/components/navbar/components/nav-m
3131
* `--surface-4` ring (`p-[3px]`, overlay shadow) wrapping an inner `--bg`
3232
* surface, with the item grid padded inside.
3333
*
34-
* The grid renders three visual columns on six tracks (each tile spans two),
35-
* which keeps six-item menus pixel-identical to a plain three-column grid while
36-
* letting a five-item menu center its two-tile last row - the second-to-last
37-
* tile starts on track two, so the short row sits symmetrically instead of
38-
* leaving a hole in the corner.
34+
* The grid is a plain three-column grid filled in reading order, so a menu with
35+
* a non-multiple-of-three item count leaves its gap in the bottom-right corner
36+
* rather than centering the short row.
3937
*/
4038

4139
interface NavMenuChipProps {
@@ -81,14 +79,7 @@ export function NavMenuChip({ menu }: NavMenuChipProps) {
8179
<div className={cn(PANEL_BASE, !closed && PANEL_REVEAL)}>
8280
<div className='w-[840px] rounded-xl border border-[var(--border-muted)] bg-[var(--surface-4)] p-[3px] shadow-[var(--shadow-overlay)]'>
8381
<div className='rounded-lg border border-[var(--border-1)] bg-[var(--bg)] p-2'>
84-
<div
85-
className={cn(
86-
'grid grid-cols-6 gap-1 [&>*]:col-span-2',
87-
items.length % 3 === 2 && '[&>*:nth-last-child(2)]:col-start-2'
88-
)}
89-
role='group'
90-
aria-label={label}
91-
>
82+
<div className='grid grid-cols-3 gap-1' role='group' aria-label={label}>
9283
{items.map((item) => (
9384
<NavMenuItem key={item.title} item={item} onSelect={handleSelect} />
9485
))}

apps/sim/app/_shell/providers/posthog-provider.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { useEffect, useRef, useState } from 'react'
44
import { createLogger } from '@sim/logger'
55
import type { PostHog } from 'posthog-js'
6-
import { getEnv, isTruthy } from '@/lib/core/config/env'
6+
import { getEnv, isTruthy, publicEnvMissingAtModuleInit } from '@/lib/core/config/env'
77

88
const logger = createLogger('PostHogProvider')
99

@@ -49,6 +49,9 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) {
4949
persistence: 'localStorage+cookie',
5050
})
5151
}
52+
if (publicEnvMissingAtModuleInit) {
53+
posthog.capture('runtime_env_missing_at_module_init')
54+
}
5255
clientRef.current = posthog
5356
setProvider(() => PHProvider)
5457
})

apps/sim/app/_shell/public-env-script.test.tsx

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,29 @@
11
/**
22
* @vitest-environment node
33
*/
4-
import { EnvScript } from 'next-runtime-env'
4+
import { renderToStaticMarkup } from 'react-dom/server'
55
import { describe, expect, it } from 'vitest'
66
import { PublicEnvScript } from '@/app/_shell/public-env-script'
77

88
/**
9-
* Guards the loading strategy, not the markup. A plain `<script>` rendered from
10-
* the root layout lands after the `<script async>` chunk tags Next emits at the
11-
* top of the document, so a chunk can execute - and hydration can begin - before
12-
* `window.__ENV` is populated. Delegating to `<EnvScript>` keeps the
13-
* `beforeInteractive` guarantee that `next-runtime-env` applies by default.
9+
* Guards the one property that matters: the emitted tag assigns `window.__ENV`
10+
* itself. Next's `beforeInteractive` strategy instead pushes the assignment onto
11+
* `self.__next_s`, a queue `appBootstrap` reads exactly once and abandons when it
12+
* is empty - so whenever the bootstrap chunk runs before the parser reaches this
13+
* tag, the assignment is discarded and `window.__ENV` is never defined for that
14+
* document. See the component's TSDoc for the full ordering argument.
1415
*/
1516
describe('PublicEnvScript', () => {
16-
it('delegates to next-runtime-env EnvScript rather than emitting a raw script tag', () => {
17-
const element = PublicEnvScript()
17+
it('emits a script that assigns window.__ENV directly', () => {
18+
const markup = renderToStaticMarkup(<PublicEnvScript />)
1819

19-
expect(element.type).toBe(EnvScript)
20-
expect(element.type).not.toBe('script')
20+
expect(markup).toContain("window['__ENV'] =")
2121
})
2222

23-
it('does not opt out of the beforeInteractive strategy', () => {
24-
const { disableNextScript, nextScriptProps } = PublicEnvScript().props
23+
it('does not defer the assignment into the __next_s queue', () => {
24+
const markup = renderToStaticMarkup(<PublicEnvScript />)
2525

26-
expect(disableNextScript).toBeUndefined()
27-
expect(nextScriptProps?.strategy ?? 'beforeInteractive').toBe('beforeInteractive')
26+
expect(markup).not.toContain('__next_s')
2827
})
2928

3029
it('passes only NEXT_PUBLIC_ variables through to the browser', () => {

apps/sim/app/_shell/public-env-script.tsx

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,31 @@ const HOSTED_PUBLIC_ENV = Object.fromEntries(
1818
/**
1919
* Static equivalent of `next-runtime-env`'s `<PublicEnvScript>` for the hosted
2020
* deployment. It renders the library's own `<EnvScript>`, so the emitted markup
21-
* and its `beforeInteractive` loading strategy are identical to the self-hosted
22-
* path - only the env read differs. `<PublicEnvScript>` additionally calls
23-
* `unstable_noStore()`, which opts the entire app into dynamic rendering; that
24-
* only pays off for self-hosted Docker images that re-inject env per deploy
25-
* without a rebuild, so hosted reads the env once here and stays static.
21+
* is identical to the self-hosted path - only the env read differs.
22+
* `<PublicEnvScript>` additionally calls `unstable_noStore()`, which opts the
23+
* entire app into dynamic rendering; that only pays off for self-hosted Docker
24+
* images that re-inject env per deploy without a rebuild, so hosted reads the
25+
* env once here and stays static.
2626
*
27-
* `beforeInteractive` is load-bearing, not an optimization. A plain `<script>`
28-
* rendered from the root layout lands at the end of `<head>`, after the ~40
29-
* `<script async>` chunk tags Next emits at the top of the document; an `async`
30-
* script runs as soon as its fetch resolves, so on a warm cache a Next chunk
31-
* can execute - and hydration can begin - before the parser reaches the env
32-
* tag, leaving `window.__ENV` undefined for the first render.
33-
* `beforeInteractive` instead queues the script into `self.__next_s`, which
34-
* Next's `appBootstrap` drains to completion before calling `hydrate()`.
27+
* `disableNextScript` is load-bearing. Without it, `<EnvScript>` defaults to
28+
* Next's `<Script strategy='beforeInteractive'>`, which does not assign
29+
* `window.__ENV` at all - it emits a tag that pushes the assignment onto
30+
* `self.__next_s`. That queue has exactly one consumer, `appBootstrap`, which
31+
* reads it once and short-circuits to `hydrate()` when it is empty. The
32+
* bootstrap chunk's `<script async>` tag sits ~13KB earlier in the document
33+
* than this tag, so whenever that chunk executes before the parser arrives
34+
* here, the queue is drained empty, nothing ever drains it again, and
35+
* `window.__ENV` stays undefined for the entire lifetime of the document -
36+
* every `getEnv` read empty, until a reload happens to win the race.
37+
*
38+
* A plain `<script>` assigns unconditionally when the parser reaches it. When
39+
* it is reached before the bootstrap chunk runs it lands strictly earlier than
40+
* the queue drain would have; when it is not, the value still arrives a few
41+
* milliseconds late instead of never. There is no supported way to place an
42+
* inline script ahead of the framework's own bootstrap tags - React emits those
43+
* in the preamble, before any content from the component tree - so the goal is
44+
* to make losing that race harmless rather than to try to win it.
3545
*/
3646
export function PublicEnvScript() {
37-
return <EnvScript env={HOSTED_PUBLIC_ENV} />
47+
return <EnvScript env={HOSTED_PUBLIC_ENV} disableNextScript />
3848
}

apps/sim/app/api/guardrails/validate/route.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ describe('POST /api/guardrails/validate', () => {
244244
expect(res.status).toBe(200)
245245
expect(mockImportProvenance).toHaveBeenCalledWith(provenance, 'secret value', ['input'], {
246246
trusted: true,
247+
origin: 'guardrailsRoute.inputProvenance',
247248
})
248249
})
249250

apps/sim/app/api/guardrails/validate/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
267267
provenanceInspection.value,
268268
inputStr,
269269
['input'],
270-
{ trusted: true }
270+
{ trusted: true, origin: 'guardrailsRoute.inputProvenance' }
271271
)
272272
).success
273273
: true

apps/sim/app/api/knowledge/search/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
604604
!(await importDurableSecretProvenance(
605605
resultSecretRegistry,
606606
metadata.provenance,
607-
renderedMetadata
607+
renderedMetadata,
608+
'knowledge'
608609
))
609610
) {
610611
resultSecretRegistry.markIncomplete()

apps/sim/app/api/mcp/serve/[serverId]/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ async function projectWorkflowMcpModelContent(
288288
): Promise<unknown> {
289289
const registry = new ResolvedSecretTraceRegistry([], scope)
290290
const imported = await registry.importCrossingProvenance(privateProvenance, value, {
291+
origin: 'mcpServe.workflowCrossing',
291292
trusted: true,
292293
})
293294
if (!imported || !registry.isComplete()) {

0 commit comments

Comments
 (0)