Skip to content

Commit fd118c5

Browse files
jplhomerpi-mono
andcommitted
Bump App Security trace schema version to 3
Co-authored-by: AI (Pi/Claude Opus 5.5) <noreply@pi.dev>
1 parent 25831bc commit fd118c5

17 files changed

Lines changed: 45 additions & 44 deletions

File tree

‎packages/app/src/cli/services/app-security-api.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ describe('App Security CLI integration', () => {
9393
expect(review.source_scan_id).toBe(result.execution.scan.scan.input_hash)
9494
expect(review.checks.length).toBeGreaterThan(0)
9595
expect(review.checks.every((check: {prompt: string}) => check.prompt.length > 0)).toBe(true)
96-
expect(trace.schema_version).toBe(2)
96+
expect(trace.schema_version).toBe(3)
9797
expect(trace.engine.name).toBe('shopify-app-security')
9898
expect(result.engine).toEqual(trace.engine)
9999
expect(result.reviewPath).toBe(artifactPath(directory, 'review.json'))
@@ -635,7 +635,7 @@ describe('App Security CLI integration', () => {
635635

636636
const payload = JSON.parse(output.mock.calls[0]![0]) as {operation: string; trace: {schema_version: number}}
637637
expect(payload.operation).toBe('scan')
638-
expect(payload.trace.schema_version).toBe(2)
638+
expect(payload.trace.schema_version).toBe(3)
639639
await expect(readFile(artifactPath(directory, 'review.json'))).resolves.toContain('"checks"')
640640
await expect(readFile(artifactPath(directory, 'trace.json'))).resolves.toContain('"schema_version"')
641641
expect(setExitCode).not.toHaveBeenCalled()

‎packages/app/src/cli/services/app-security-artifacts.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {parseTrace, type TraceV2} from './app-security-engine/index.js'
1+
import {parseTrace, type TraceV3} from './app-security-engine/index.js'
22
import {fileExists, fileSize, readFile} from '@shopify/cli-kit/node/fs'
33
import {AbortError} from '@shopify/cli-kit/node/error'
44
import {joinPath, relativePath, resolvePath} from '@shopify/cli-kit/node/path'
@@ -20,7 +20,7 @@ export interface ResolvedAppSecurityArtifactPaths extends Required<AppSecurityAr
2020
}
2121

2222
export type ReadTraceResult =
23-
| {status: 'ok'; trace: TraceV2}
23+
| {status: 'ok'; trace: TraceV3}
2424
| {status: 'missing'}
2525
| {status: 'invalid'; errors: string[]}
2626

‎packages/app/src/cli/services/app-security-engine/index.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@ export type {AppSecuritySubmission, AppSecuritySubmissionReport, BuildSubmission
3030
export type {ReviewPack} from './checks/index.js'
3131
export {groupIssues} from './output/group-issues.js'
3232
export type {IssueGroup} from './output/group-issues.js'
33-
export type {Capabilities, Issue, ScanResult, Severity, TraceV2} from './types.js'
33+
export type {Capabilities, Issue, ScanResult, Severity, TraceV3} from './types.js'

‎packages/app/src/cli/services/app-security-engine/run.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {computeResultHash} from './scorer/index.js'
1515
import {compileTrace, validateTrace} from './trace/index.js'
1616
import {FINDINGS_SCHEMA_VERSION} from './types.js'
1717
import {getEngineVersion} from './version.js'
18-
import type {CheckExecution, ScanResult, Suppression, TraceV2} from './types.js'
18+
import type {CheckExecution, ScanResult, Suppression, TraceV3} from './types.js'
1919

2020
export {AppRootDiscoveryError, findAppRoot}
2121

@@ -43,7 +43,7 @@ export interface AppSecurityScan {
4343
operation: 'scan'
4444
appRoot: string
4545
scan: ScanResult
46-
trace: TraceV2
46+
trace: TraceV3
4747
reviewPack: ReviewPack
4848
engine: AppSecurityEngineMetadata
4949
}
@@ -52,12 +52,12 @@ export interface AppSecurityCompile {
5252
operation: 'compile'
5353
appRoot: string
5454
scan: ScanResult
55-
trace: TraceV2
55+
trace: TraceV3
5656
findings: AppSecurityFindings
5757
engine: AppSecurityEngineMetadata
5858
}
5959

60-
export type ParseTraceResult = {ok: true; trace: TraceV2} | {ok: false; errors: string[]}
60+
export type ParseTraceResult = {ok: true; trace: TraceV3} | {ok: false; errors: string[]}
6161

6262
/** Expected user error while reading an agent findings document. */
6363
export class FindingsDocumentError extends Error {
@@ -76,7 +76,7 @@ export function getAgentInstructions(): string {
7676

7777
export function parseTrace(value: unknown): ParseTraceResult {
7878
const validation = validateTrace(value)
79-
return validation.valid ? {ok: true, trace: value as TraceV2} : {ok: false, errors: validation.errors}
79+
return validation.valid ? {ok: true, trace: value as TraceV3} : {ok: false, errors: validation.errors}
8080
}
8181

8282
export function parseFindings(value: unknown): FindingsDocument {

‎packages/app/src/cli/services/app-security-engine/submission/index.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type {
1111
Severity,
1212
SuppressionProvenance,
1313
TraceFinding,
14-
TraceV2,
14+
TraceV3,
1515
} from '../types.js'
1616

1717
export const SUBMISSION_SCHEMA_VERSION = 1 as const
@@ -68,7 +68,7 @@ export interface AppSecuritySubmission {
6868
}
6969

7070
export interface AppSecuritySubmissionReport {
71-
trace_schema_version: TraceV2['schema_version']
71+
trace_schema_version: TraceV3['schema_version']
7272
engine: {name: string; version: string; ruleset: string}
7373
cli_version: string
7474
generated_at: string
@@ -93,7 +93,7 @@ export interface AppSecuritySubmissionReport {
9393
files_scanned: number
9494
complete: boolean
9595
files_skipped: {too_large: number; unreadable: number}
96-
gaps: {code: TraceV2['coverage']['gaps'][number]['code']; check_id?: string}[]
96+
gaps: {code: TraceV3['coverage']['gaps'][number]['code']; check_id?: string}[]
9797
}
9898
attestation: {trace_digest: string}
9999
}
@@ -159,7 +159,7 @@ function submissionCheck(check: CheckExecution): SubmissionCheck {
159159
}
160160
}
161161

162-
function skippedFileCounts(trace: TraceV2): {too_large: number; unreadable: number} {
162+
function skippedFileCounts(trace: TraceV3): {too_large: number; unreadable: number} {
163163
return trace.coverage.files_skipped.reduce(
164164
(counts, file) =>
165165
file.reason === 'too_large'
@@ -169,7 +169,7 @@ function skippedFileCounts(trace: TraceV2): {too_large: number; unreadable: numb
169169
)
170170
}
171171

172-
export function buildSubmission(trace: TraceV2, options: BuildSubmissionOptions): AppSecuritySubmission {
172+
export function buildSubmission(trace: TraceV3, options: BuildSubmissionOptions): AppSecuritySubmission {
173173
return {
174174
schemaVersion: SUBMISSION_SCHEMA_VERSION,
175175
report: {

‎packages/app/src/cli/services/app-security-engine/tests/fixtures/submission-trace.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {findingFingerprint, sha256} from '../../trace/index.js'
2-
import type {TraceFinding, TraceV2} from '../../types.js'
2+
import type {TraceFinding, TraceV3} from '../../types.js'
33

44
const inputHash = `sha256:${'a'.repeat(64)}`
55
const privateFileHash = `sha256:${'b'.repeat(64)}`
@@ -23,7 +23,7 @@ const suppression = {
2323
}
2424

2525
const traceWithLeakageSentinels = {
26-
schema_version: 2,
26+
schema_version: 3,
2727
engine: {
2828
name: 'shopify-app-security',
2929
version: '0.1.0',
@@ -221,7 +221,7 @@ function computedFindingFingerprint(finding: TraceFinding): string {
221221

222222
// Compute every integrity field from the final semantic inputs. Unknown-field
223223
// sentinels are already present before the unsigned trace digest is calculated.
224-
const unsignedTrace = traceWithLeakageSentinels as unknown as Omit<TraceV2, 'attestation'>
224+
const unsignedTrace = traceWithLeakageSentinels as unknown as Omit<TraceV3, 'attestation'>
225225
const agentCheck = unsignedTrace.checks_executed[1]!
226226
const unresolvedCheck = unsignedTrace.checks_executed[4]!
227227
agentCheck.prompt_hash = sha256(agentCheck.prompt!)
@@ -242,4 +242,4 @@ export const submissionTraceHashes = {
242242
export const submissionTraceFixture = {
243243
...unsignedTrace,
244244
attestation: {digest: submissionTraceHashes.traceDigest, signed: false},
245-
} as TraceV2
245+
} as TraceV3

‎packages/app/src/cli/services/app-security-engine/tests/fixtures/submission.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"schemaVersion": 1,
33
"report": {
4-
"trace_schema_version": 2,
4+
"trace_schema_version": 3,
55
"engine": {
66
"name": "shopify-app-security",
77
"version": "0.1.0",
@@ -141,7 +141,7 @@
141141
"gaps": [{"code": "skipped_file"}, {"code": "unresolved_check", "check_id": "UNREPORTED_AGENT_CHECK"}]
142142
},
143143
"attestation": {
144-
"trace_digest": "sha256:a5abc9a5b332ff7fb9a7afae1dd13a9af8a4fcb990d4551f63d96e3c5318507e"
144+
"trace_digest": "sha256:47beaec0942e8f81a01ab97293d256be086c6426cd30c220aa3f800d6a7d19cb"
145145
}
146146
}
147147
}

‎packages/app/src/cli/services/app-security-engine/tests/scan-contract.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {afterEach, describe, expect, test} from 'vitest'
88
import {mkdir, mkdtemp, rm, writeFile} from 'node:fs/promises'
99
import {tmpdir} from 'node:os'
1010
import {join} from 'node:path'
11-
import type {TraceV2} from '../types.js'
11+
import type {TraceV3} from '../types.js'
1212

1313
const directories: string[] = []
1414
afterEach(async () => {
@@ -31,7 +31,7 @@ async function app(files: Record<string, string>): Promise<string> {
3131
const appConfig = (scopes = '') => `name = "Scan contract"\n[access_scopes]\nscopes = "${scopes}"\n`
3232
const reactPackage = JSON.stringify({dependencies: {'@shopify/shopify-app-react-router': '^1.0.0'}})
3333

34-
function resign(trace: TraceV2): void {
34+
function resign(trace: TraceV3): void {
3535
const {attestation: _attestation, ...unsigned} = trace
3636
trace.attestation = {digest: sha256(unsigned), signed: false}
3737
}

‎packages/app/src/cli/services/app-security-engine/tests/trace.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ describe('trace v2', () => {
8686
const trace = compileTrace(result(), {
8787
generatedAt: '2026-08-28T00:00:00.000Z',
8888
})
89-
expect(trace.schema_version).toBe(2)
89+
expect(trace.schema_version).toBe(3)
9090
expect(trace.engine.name).toBe('shopify-app-security')
9191
expect(trace.project).toMatchObject({
9292
commit: 'a'.repeat(40),
@@ -248,6 +248,7 @@ describe('trace v2', () => {
248248
generatedAt: '2026-08-28T00:00:00.000Z',
249249
})
250250
expect(validateTrace({...trace, schema_version: 1}).errors).toContain('unsupported schema_version: 1')
251+
expect(validateTrace({...trace, schema_version: 2}).errors).toContain('unsupported schema_version: 2')
251252
const changed = structuredClone(trace)
252253
const [changedFinding] = changed.findings
253254
if (!changedFinding) throw new Error('Expected the trace to contain a finding')

‎packages/app/src/cli/services/app-security-engine/trace/index.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type {
1313
Severity,
1414
Suppression,
1515
TraceFinding,
16-
TraceV2,
16+
TraceV3,
1717
} from '../types.js'
1818

1919
const SHA256 = /^sha256:[0-9a-f]{64}$/
@@ -134,7 +134,7 @@ function issueToFinding(issueInput: Issue): TraceFinding {
134134
return {fingerprint: findingFingerprint(core), ...core, suppressed: false}
135135
}
136136

137-
export function hasRecordedAgentReview(trace: TraceV2): boolean {
137+
export function hasRecordedAgentReview(trace: TraceV3): boolean {
138138
return (
139139
trace.findings.some((finding) => finding.source === 'agent' || finding.source === 'external') ||
140140
trace.checks_executed.some((execution) => {
@@ -156,7 +156,7 @@ export interface CompileTraceOptions {
156156
}
157157

158158
/** Compile trace schema v2. Version 1 remains a separate frozen type. */
159-
export function compileTrace(result: ScanResult, options: CompileTraceOptions = {}): TraceV2 {
159+
export function compileTrace(result: ScanResult, options: CompileTraceOptions = {}): TraceV3 {
160160
const findings = result.issues
161161
.map(issueToFinding)
162162
.sort((left, right) =>
@@ -253,7 +253,7 @@ export function compileTrace(result: ScanResult, options: CompileTraceOptions =
253253
})),
254254
},
255255
}
256-
const trace: TraceV2 = {...unsigned, attestation: {digest: sha256(unsigned), signed: false}}
256+
const trace: TraceV3 = {...unsigned, attestation: {digest: sha256(unsigned), signed: false}}
257257
const validation = validateTraceValue(trace)
258258
if (!validation.valid) {
259259
// Self-compiled traces are acyclic. A complexity miss on a large app must
@@ -818,7 +818,7 @@ export function validateTrace(value: unknown): TraceValidationResult {
818818
}
819819
}
820820

821-
export function assertCompatibleTrace(value: unknown): asserts value is TraceV2 {
821+
export function assertCompatibleTrace(value: unknown): asserts value is TraceV3 {
822822
const validation = validateTrace(value)
823823
if (!validation.valid) throw new Error(`Invalid App Security trace: ${validation.errors.join('; ')}`)
824824
}

0 commit comments

Comments
 (0)