@pikku/inspector 0.12.52 → 0.12.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +341 -0
- package/dist/add/add-functions.js +46 -4
- package/dist/add/add-keyed-wiring.js +3 -1
- package/dist/add/add-personas.js +7 -1
- package/dist/add/add-workflow.js +12 -1
- package/dist/error-codes.d.ts +8 -1
- package/dist/error-codes.js +12 -0
- package/dist/types.d.ts +9 -0
- package/dist/utils/check-secret-sinks.d.ts +31 -0
- package/dist/utils/check-secret-sinks.js +117 -0
- package/dist/utils/post-process.js +4 -1
- package/dist/utils/single-declaration.d.ts +32 -0
- package/dist/utils/single-declaration.js +43 -0
- package/dist/utils/validate-exposed-functions-gated.js +15 -7
- package/dist/utils/workflow/dsl/extract-dsl-workflow.d.ts +6 -0
- package/dist/utils/workflow/dsl/extract-dsl-workflow.js +48 -1
- package/dist/utils/workflow/dsl/patterns.d.ts +9 -4
- package/dist/utils/workflow/dsl/patterns.js +26 -4
- package/package.json +3 -3
- package/src/add/add-functions.ts +57 -5
- package/src/add/add-keyed-wiring.ts +3 -0
- package/src/add/add-personas.test.ts +115 -1
- package/src/add/add-personas.ts +18 -9
- package/src/add/add-scenario-step.test.ts +38 -2
- package/src/add/add-scope.test.ts +73 -0
- package/src/add/add-system-role.test.ts +83 -0
- package/src/add/add-workflow.ts +21 -1
- package/src/add/allow-escape-hatches.test.ts +144 -0
- package/src/add/audit-meta.test.ts +75 -0
- package/src/add/secret-sink-check.test.ts +211 -0
- package/src/error-codes.ts +13 -0
- package/src/types.ts +9 -0
- package/src/utils/check-secret-sinks.ts +145 -0
- package/src/utils/post-process.ts +4 -1
- package/src/utils/single-declaration.ts +57 -0
- package/src/utils/validate-exposed-functions-gated.test.ts +2 -2
- package/src/utils/validate-exposed-functions-gated.ts +16 -7
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +57 -0
- package/src/utils/workflow/dsl/patterns.ts +33 -4
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
isWorkflowDoCall,
|
|
27
27
|
isWorkflowExpectEventuallyCall,
|
|
28
28
|
isScenarioStepCall,
|
|
29
|
+
isScenarioExpectationCall,
|
|
29
30
|
getScenarioStepPhase,
|
|
30
31
|
DYNAMIC_SCENARIO_STEP_TARGET,
|
|
31
32
|
extractActorFromOptions,
|
|
@@ -82,9 +83,64 @@ export interface ExtractionResult {
|
|
|
82
83
|
steps?: WorkflowStepMeta[]
|
|
83
84
|
/** Workflow context (top-level variables) */
|
|
84
85
|
context?: WorkflowContext
|
|
86
|
+
/**
|
|
87
|
+
* The flow asserts something through an expectation helper rather than a
|
|
88
|
+
* `then` step. Recorded separately because those helpers are inline steps and
|
|
89
|
+
* leave no phase behind, so PKU680 has no other way to see them.
|
|
90
|
+
*/
|
|
91
|
+
asserts?: boolean
|
|
85
92
|
reason?: string
|
|
86
93
|
}
|
|
87
94
|
|
|
95
|
+
/**
|
|
96
|
+
* A function the walk must not step into, because nothing here says it runs.
|
|
97
|
+
*
|
|
98
|
+
* A callback handed straight to a call — a fanout's `map`, an inline
|
|
99
|
+
* `workflow.do` step — executes as part of the body and counts. A function that
|
|
100
|
+
* is merely *declared* or assigned to a name does not: an assertion parked in an
|
|
101
|
+
* unused helper asserts nothing, and counting it would let a scenario silence
|
|
102
|
+
* PKU680 with dead code.
|
|
103
|
+
*/
|
|
104
|
+
function isUncalledFunction(node: ts.Node): boolean {
|
|
105
|
+
if (
|
|
106
|
+
!ts.isFunctionDeclaration(node) &&
|
|
107
|
+
!ts.isFunctionExpression(node) &&
|
|
108
|
+
!ts.isArrowFunction(node)
|
|
109
|
+
) {
|
|
110
|
+
return false
|
|
111
|
+
}
|
|
112
|
+
if (ts.isFunctionDeclaration(node)) return true
|
|
113
|
+
const parent = node.parent
|
|
114
|
+
return !(
|
|
115
|
+
parent &&
|
|
116
|
+
ts.isCallExpression(parent) &&
|
|
117
|
+
parent.arguments.some((argument) => argument === node)
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Whether the body calls one of the expectation helpers anywhere it runs.
|
|
123
|
+
*
|
|
124
|
+
* A whole-body walk rather than a check inside step extraction: the helpers
|
|
125
|
+
* produce no step of their own, so there is no extraction result to hang the
|
|
126
|
+
* answer off, and one nested inside a branch asserts just as much as one at the
|
|
127
|
+
* top level.
|
|
128
|
+
*/
|
|
129
|
+
function hasScenarioExpectation(body: ts.Node): boolean {
|
|
130
|
+
let found = false
|
|
131
|
+
const visit = (node: ts.Node): void => {
|
|
132
|
+
if (found) return
|
|
133
|
+
if (ts.isCallExpression(node) && isScenarioExpectationCall(node)) {
|
|
134
|
+
found = true
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
if (node !== body && isUncalledFunction(node)) return
|
|
138
|
+
ts.forEachChild(node, visit)
|
|
139
|
+
}
|
|
140
|
+
visit(body)
|
|
141
|
+
return found
|
|
142
|
+
}
|
|
143
|
+
|
|
88
144
|
/**
|
|
89
145
|
* Extract DSL workflow metadata from a function declaration
|
|
90
146
|
*/
|
|
@@ -170,6 +226,7 @@ export function extractDSLWorkflow(
|
|
|
170
226
|
steps,
|
|
171
227
|
context:
|
|
172
228
|
Object.keys(workflowContext).length > 0 ? workflowContext : undefined,
|
|
229
|
+
asserts: hasScenarioExpectation(arrowFunc.body) || undefined,
|
|
173
230
|
}
|
|
174
231
|
} catch (error) {
|
|
175
232
|
return {
|
|
@@ -44,14 +44,14 @@ export function isWorkflowDoCall(
|
|
|
44
44
|
*/
|
|
45
45
|
export const DYNAMIC_SCENARIO_STEP_TARGET = '<dynamic>'
|
|
46
46
|
|
|
47
|
-
const SCENARIO_STEP_PHASES = ['
|
|
47
|
+
const SCENARIO_STEP_PHASES = ['given', 'when', 'then'] as const
|
|
48
48
|
|
|
49
49
|
export type ScenarioStepPhaseName = (typeof SCENARIO_STEP_PHASES)[number]
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
* Check if a call expression is scenario.
|
|
53
|
-
*
|
|
54
|
-
*
|
|
52
|
+
* Check if a call expression is scenario.given()/when()/then() — a declared
|
|
53
|
+
* `pikkuScenarioStep` invoked by name. `given`/`when` are pure sugar for each
|
|
54
|
+
* other; `then` additionally makes the step's bindings witnesses.
|
|
55
55
|
*/
|
|
56
56
|
export function isScenarioStepCall(node: ts.CallExpression): boolean {
|
|
57
57
|
return getScenarioStepPhase(node) !== undefined
|
|
@@ -74,6 +74,35 @@ export function getScenarioStepPhase(
|
|
|
74
74
|
return SCENARIO_STEP_PHASES.find((phase) => phase === propAccess.name.text)
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* The expectation helpers that assert without being a `then`.
|
|
79
|
+
*
|
|
80
|
+
* `expectService` and `expectError` are inline steps rather than scenario
|
|
81
|
+
* steps, so they carry no phase and never reach the step meta — but each one is
|
|
82
|
+
* a witness: a recorded service call, or a refusal. PKU680 has to see them, or
|
|
83
|
+
* a scenario whose only assertion is one of these reads as asserting nothing.
|
|
84
|
+
*/
|
|
85
|
+
const SCENARIO_EXPECTATION_CALLS = [
|
|
86
|
+
'expectService',
|
|
87
|
+
'expectError',
|
|
88
|
+
'expectEventually',
|
|
89
|
+
] as const
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check if a call expression is one of the scenario expectation helpers —
|
|
93
|
+
* `scenario.expectService()`, `.expectError()` or `.expectEventually()`.
|
|
94
|
+
*/
|
|
95
|
+
export function isScenarioExpectationCall(node: ts.CallExpression): boolean {
|
|
96
|
+
return (
|
|
97
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
98
|
+
SCENARIO_EXPECTATION_CALLS.some(
|
|
99
|
+
(name) =>
|
|
100
|
+
name === (node.expression as ts.PropertyAccessExpression).name.text
|
|
101
|
+
) &&
|
|
102
|
+
isWorkflowWireIdentifier(node.expression.expression)
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
77
106
|
/**
|
|
78
107
|
* Check if a call expression is workflow.expectEventually()
|
|
79
108
|
*/
|