Skip to content

Commit ad9855d

Browse files
waleedlatif1claude
andcommitted
chore(packages): post-audit test + packaging polish
- Add safeCompare unit tests (identity, length mismatch, hex-nibble diff). - Add Buffer-secret cases to hmac tests to lock in Svix/MS-Teams contract. - Declare `reactflow` as a peerDependency on @sim/workflow-types — only used for type imports. - Add a barrel export to @sim/workflow-persistence for consumers that prefer package-level imports; subpath exports retained. - Document the data-field invariant in load.ts for loop/parallel subflow patching. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 3d4be6d commit ad9855d

7 files changed

Lines changed: 88 additions & 4 deletions

File tree

bun.lock

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { safeCompare } from './compare'
3+
4+
describe('safeCompare', () => {
5+
it('returns true for identical strings', () => {
6+
expect(safeCompare('abc', 'abc')).toBe(true)
7+
})
8+
9+
it('returns false for equal-length different strings', () => {
10+
expect(safeCompare('abc', 'abd')).toBe(false)
11+
})
12+
13+
it('returns false for different-length strings without throwing', () => {
14+
expect(safeCompare('short', 'longer-value')).toBe(false)
15+
expect(safeCompare('', 'a')).toBe(false)
16+
expect(safeCompare('a', '')).toBe(false)
17+
})
18+
19+
it('returns true for two empty strings', () => {
20+
expect(safeCompare('', '')).toBe(true)
21+
})
22+
23+
it('handles long inputs', () => {
24+
const a = 'x'.repeat(10_000)
25+
const b = 'x'.repeat(10_000)
26+
expect(safeCompare(a, b)).toBe(true)
27+
expect(safeCompare(a, `${b.slice(0, -1)}y`)).toBe(false)
28+
})
29+
30+
it('is case-sensitive', () => {
31+
expect(safeCompare('ABC', 'abc')).toBe(false)
32+
})
33+
34+
it('distinguishes hex digests that differ in one nibble', () => {
35+
const a = 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7'
36+
const b = 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff8'
37+
expect(safeCompare(a, b)).toBe(false)
38+
expect(safeCompare(a, a)).toBe(true)
39+
})
40+
})

packages/security/src/hmac.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ describe('hmacSha256Hex', () => {
2424
it('differs when secret changes', () => {
2525
expect(hmacSha256Hex('body', 'k1')).not.toBe(hmacSha256Hex('body', 'k2'))
2626
})
27+
28+
it('accepts a Buffer secret and matches the equivalent binary-string secret', () => {
29+
const raw = Buffer.from('0b'.repeat(20), 'hex')
30+
expect(hmacSha256Hex('Hi There', raw)).toBe(hmacSha256Hex('Hi There', raw.toString('binary')))
31+
})
2732
})
2833

2934
describe('hmacSha256Base64', () => {
@@ -40,4 +45,11 @@ describe('hmacSha256Base64', () => {
4045
const b64 = hmacSha256Base64('body', 'secret')
4146
expect(Buffer.from(b64, 'base64').toString('hex')).toBe(hex)
4247
})
48+
49+
it('accepts a Buffer secret (Svix / MS-Teams scheme)', () => {
50+
const secret = Buffer.from('whsec-decoded-bytes')
51+
const hex = hmacSha256Hex('body', secret)
52+
const b64 = hmacSha256Base64('body', secret)
53+
expect(Buffer.from(b64, 'base64').toString('hex')).toBe(hex)
54+
})
4355
})

packages/workflow-persistence/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
"node": ">=20.0.0"
1111
},
1212
"exports": {
13+
".": {
14+
"types": "./src/index.ts",
15+
"default": "./src/index.ts"
16+
},
1317
"./load": {
1418
"types": "./src/load.ts",
1519
"default": "./src/load.ts"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export {
2+
loadWorkflowFromNormalizedTablesRaw,
3+
persistMigratedBlocks,
4+
type RawNormalizedWorkflow,
5+
} from './load'
6+
export { saveWorkflowToNormalizedTables } from './save'
7+
export {
8+
DEFAULT_SUBBLOCK_TYPE,
9+
mergeSubBlockValues,
10+
mergeSubblockStateWithValues,
11+
} from './subblocks'
12+
export {
13+
convertLoopBlockToLoop,
14+
convertParallelBlockToParallel,
15+
findChildNodes,
16+
generateLoopBlocks,
17+
generateParallelBlocks,
18+
} from './subflow-helpers'
19+
export type { DbOrTx, NormalizedWorkflowData } from './types'

packages/workflow-persistence/src/load.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ export interface RawNormalizedWorkflow extends NormalizedWorkflowData {
1818
* backfill, tool sanitization) depend on the block/tool registry that lives in
1919
* the Next app and should not be pulled into leaf services. Callers that want
2020
* migrated state should wrap this with their own migration pipeline.
21+
*
22+
* Invariant: downstream migrations must not mutate `block.data.collection`,
23+
* `block.data.whileCondition`, or `block.data.doWhileCondition`. Those fields
24+
* are patched here from the subflow config on the pre-migration block, and
25+
* callers re-sync only `loop.enabled`/`parallel.enabled` from the migrated
26+
* block. If a future migration rewrites these data fields, the loop/parallel
27+
* config on the returned object will silently diverge from the migrated block.
2128
*/
2229
export async function loadWorkflowFromNormalizedTablesRaw(
2330
workflowId: string

packages/workflow-types/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@
2626
"format": "biome format --write .",
2727
"format:check": "biome format ."
2828
},
29-
"dependencies": {
29+
"peerDependencies": {
3030
"reactflow": "^11.11.4"
3131
},
3232
"devDependencies": {
3333
"@sim/tsconfig": "workspace:*",
34+
"reactflow": "^11.11.4",
3435
"typescript": "^5.7.3"
3536
}
3637
}

0 commit comments

Comments
 (0)