@pikku/inspector 0.12.49 → 0.12.52

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.
Files changed (104) hide show
  1. package/CHANGELOG.md +501 -0
  2. package/OPTIMIZATION-PLAN.md +9 -3
  3. package/dist/add/add-auth.d.ts +1 -1
  4. package/dist/add/add-auth.js +1 -1
  5. package/dist/add/add-cli.js +7 -0
  6. package/dist/add/add-credential.js +1 -1
  7. package/dist/add/add-functions.js +156 -7
  8. package/dist/add/add-http-route.js +16 -6
  9. package/dist/add/add-middleware.js +46 -3
  10. package/dist/add/add-personas.d.ts +13 -0
  11. package/dist/add/add-personas.js +210 -0
  12. package/dist/add/add-scope.js +1 -1
  13. package/dist/add/add-secret.js +1 -1
  14. package/dist/add/add-system-role.d.ts +2 -0
  15. package/dist/add/add-system-role.js +102 -0
  16. package/dist/add/add-variable.js +1 -1
  17. package/dist/add/add-wire-addon.js +31 -0
  18. package/dist/error-codes.d.ts +10 -1
  19. package/dist/error-codes.js +11 -0
  20. package/dist/index.d.ts +1 -1
  21. package/dist/inspector.js +33 -1
  22. package/dist/types.d.ts +46 -2
  23. package/dist/utils/annotate-http-route-auth.d.ts +22 -0
  24. package/dist/utils/annotate-http-route-auth.js +48 -0
  25. package/dist/utils/extract-function-name.js +13 -4
  26. package/dist/utils/extract-function-node.js +18 -1
  27. package/dist/utils/extract-secret-usage.d.ts +9 -0
  28. package/dist/utils/extract-secret-usage.js +61 -0
  29. package/dist/utils/literal-properties.d.ts +22 -0
  30. package/dist/utils/literal-properties.js +69 -0
  31. package/dist/utils/load-addon-functions-meta.js +26 -1
  32. package/dist/utils/permissions.js +8 -0
  33. package/dist/utils/post-process.d.ts +68 -3
  34. package/dist/utils/post-process.js +243 -13
  35. package/dist/utils/schema-generator.d.ts +24 -0
  36. package/dist/utils/schema-generator.js +118 -19
  37. package/dist/utils/secret-alias-services.d.ts +8 -0
  38. package/dist/utils/secret-alias-services.js +45 -0
  39. package/dist/utils/secret-brokers.d.ts +2 -0
  40. package/dist/utils/secret-brokers.js +9 -0
  41. package/dist/utils/serialize-inspector-state.d.ts +10 -0
  42. package/dist/utils/serialize-inspector-state.js +18 -0
  43. package/dist/utils/serialize-middleware-groups-meta.js +3 -0
  44. package/dist/utils/validate-exposed-functions-gated.d.ts +14 -0
  45. package/dist/utils/validate-exposed-functions-gated.js +85 -0
  46. package/dist/utils/validate-tags-resolve-to-middleware.d.ts +20 -0
  47. package/dist/utils/validate-tags-resolve-to-middleware.js +41 -0
  48. package/dist/visit.js +4 -0
  49. package/package.json +3 -3
  50. package/src/add/add-auth.test.ts +5 -3
  51. package/src/add/add-auth.ts +1 -1
  52. package/src/add/add-cli.ts +8 -0
  53. package/src/add/add-credential.ts +1 -1
  54. package/src/add/add-functions.test.ts +263 -1
  55. package/src/add/add-functions.ts +219 -9
  56. package/src/add/add-http-route.ts +20 -7
  57. package/src/add/add-middleware.test.ts +196 -0
  58. package/src/add/add-middleware.ts +64 -4
  59. package/src/add/add-personas.test.ts +268 -0
  60. package/src/add/add-personas.ts +276 -0
  61. package/src/add/add-scenario-step.test.ts +49 -3
  62. package/src/add/add-scope.test.ts +30 -30
  63. package/src/add/add-scope.ts +1 -1
  64. package/src/add/add-secret.ts +1 -1
  65. package/src/add/add-subject-scenario-steps.test.ts +161 -0
  66. package/src/add/add-system-role.test.ts +214 -0
  67. package/src/add/add-system-role.ts +164 -0
  68. package/src/add/add-variable.ts +1 -1
  69. package/src/add/add-wire-addon.test.ts +111 -0
  70. package/src/add/add-wire-addon.ts +29 -0
  71. package/src/add/addon-bans.test.ts +4 -4
  72. package/src/add/config-value-metadata.test.ts +11 -11
  73. package/src/add/wire-name-literal.test.ts +2 -2
  74. package/src/error-codes.ts +13 -0
  75. package/src/index.ts +4 -1
  76. package/src/inspector.ts +39 -0
  77. package/src/types.ts +47 -2
  78. package/src/utils/annotate-http-route-auth.test.ts +163 -0
  79. package/src/utils/annotate-http-route-auth.ts +52 -0
  80. package/src/utils/extract-function-name.ts +14 -4
  81. package/src/utils/extract-function-node.ts +18 -6
  82. package/src/utils/extract-secret-usage.test.ts +81 -0
  83. package/src/utils/extract-secret-usage.ts +73 -0
  84. package/src/utils/literal-properties.ts +97 -0
  85. package/src/utils/load-addon-functions-meta.ts +32 -2
  86. package/src/utils/permissions-shorthand.test.ts +79 -0
  87. package/src/utils/permissions.ts +7 -0
  88. package/src/utils/post-process.test.ts +133 -0
  89. package/src/utils/post-process.ts +310 -12
  90. package/src/utils/schema-cache.test.ts +124 -0
  91. package/src/utils/schema-generator.test.ts +62 -1
  92. package/src/utils/schema-generator.ts +138 -21
  93. package/src/utils/secret-alias-services.test.ts +146 -0
  94. package/src/utils/secret-alias-services.ts +58 -0
  95. package/src/utils/secret-brokers.test.ts +53 -0
  96. package/src/utils/secret-brokers.ts +11 -0
  97. package/src/utils/serialize-inspector-state.ts +28 -0
  98. package/src/utils/serialize-middleware-groups-meta.ts +3 -0
  99. package/src/utils/validate-exposed-functions-gated.test.ts +230 -0
  100. package/src/utils/validate-exposed-functions-gated.ts +94 -0
  101. package/src/utils/validate-tags-resolve-to-middleware.test.ts +105 -0
  102. package/src/utils/validate-tags-resolve-to-middleware.ts +48 -0
  103. package/src/visit.ts +4 -0
  104. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,276 @@
1
+ import * as ts from 'typescript'
2
+ import { DISPOSITIONS } from '@pikku/core/virtual-user'
3
+ import { isRunnablePersona } from '@pikku/core/persona'
4
+ import type {
5
+ PersonaAccountMeta,
6
+ PersonaMeta,
7
+ } from '@pikku/core/persona'
8
+ import { ErrorCode } from '../error-codes.js'
9
+ import type { AddWiring, InspectorLogger } from '../types.js'
10
+ import {
11
+ booleanProperty,
12
+ getProperty,
13
+ numberProperty,
14
+ stringArrayProperty,
15
+ stringProperty,
16
+ unwrapAs,
17
+ } from '../utils/literal-properties.js'
18
+
19
+ type Tuning = NonNullable<PersonaMeta['tuning']>
20
+
21
+ /** A dial and the range outside which it is a mistake rather than a choice. */
22
+ const RATES: [keyof Tuning, number, number][] = [
23
+ ['temperature', 0, 2],
24
+ ['repeatRate', 0, 1],
25
+ ['reReadRate', 0, 1],
26
+ ]
27
+
28
+ /**
29
+ * `{ moves, temperature, repeatRate, … }` — a disposition's dials, overridden.
30
+ *
31
+ * The ranges are checked here rather than at run time because these are the
32
+ * kind of typo that does not throw: `repeatRate: 18` meaning 18% would silently
33
+ * double every call the persona makes, and you would read the run as a product
34
+ * bug.
35
+ */
36
+ const tuningProperty = (
37
+ config: ts.ObjectLiteralExpression,
38
+ onError: (message: string) => void
39
+ ): Tuning | undefined => {
40
+ const value = getProperty(config, 'tuning')
41
+ if (!value || !ts.isObjectLiteralExpression(value)) {
42
+ return undefined
43
+ }
44
+
45
+ const tuning: Tuning = {}
46
+
47
+ const movesValue = getProperty(value, 'moves')
48
+ if (movesValue && ts.isObjectLiteralExpression(movesValue)) {
49
+ const moves: NonNullable<Tuning['moves']> = {}
50
+ for (const move of ['continue', 'suspend', 'resume', 'abandon'] as const) {
51
+ const weight = numberProperty(movesValue, move)
52
+ if (weight === undefined) {
53
+ continue
54
+ }
55
+ if (weight < 0) {
56
+ onError(`tuning.moves.${move} must not be negative (got ${weight}).`)
57
+ continue
58
+ }
59
+ moves[move] = weight
60
+ }
61
+ if (Object.keys(moves).length) {
62
+ tuning.moves = moves
63
+ }
64
+ }
65
+
66
+ for (const [name, min, max] of RATES) {
67
+ const rate = numberProperty(value, name)
68
+ if (rate === undefined) {
69
+ continue
70
+ }
71
+ if (rate < min || rate > max) {
72
+ onError(`tuning.${name} must be between ${min} and ${max} (got ${rate}).`)
73
+ continue
74
+ }
75
+ Object.assign(tuning, { [name]: rate })
76
+ }
77
+
78
+ for (const flag of ['emptyMemory', 'readOnly', 'invertedOracle'] as const) {
79
+ const declared = booleanProperty(value, flag)
80
+ if (declared !== undefined) {
81
+ tuning[flag] = declared
82
+ }
83
+ }
84
+
85
+ const instructions = stringProperty(value, 'instructions')
86
+ if (instructions !== undefined) {
87
+ tuning.instructions = instructions
88
+ }
89
+
90
+ return Object.keys(tuning).length ? tuning : undefined
91
+ }
92
+
93
+ const accountProperty = (
94
+ value: ts.Expression | undefined
95
+ ): PersonaAccountMeta | undefined => {
96
+ if (!value || !ts.isObjectLiteralExpression(value)) {
97
+ return undefined
98
+ }
99
+ const provider = stringProperty(value, 'provider')
100
+ return provider === undefined ? {} : { provider }
101
+ }
102
+
103
+ const linkedAccountsProperty = (
104
+ config: ts.ObjectLiteralExpression,
105
+ personaId: string,
106
+ logger: InspectorLogger
107
+ ): Record<string, PersonaAccountMeta> | undefined => {
108
+ const value = getProperty(config, 'linkedAccounts')
109
+ if (!value || !ts.isObjectLiteralExpression(value)) {
110
+ return undefined
111
+ }
112
+
113
+ const accounts: Record<string, PersonaAccountMeta> = {}
114
+ for (const prop of value.properties) {
115
+ if (!ts.isPropertyAssignment(prop)) {
116
+ continue
117
+ }
118
+ if (!ts.isIdentifier(prop.name) && !ts.isStringLiteral(prop.name)) {
119
+ logger.critical(
120
+ ErrorCode.NON_LITERAL_WIRE_NAME,
121
+ `Persona '${personaId}' has a linked account whose key is not a literal.`
122
+ )
123
+ continue
124
+ }
125
+ const account = accountProperty(prop.initializer)
126
+ if (!account) {
127
+ logger.critical(
128
+ ErrorCode.INVALID_VALUE,
129
+ `Persona '${personaId}' linked account '${prop.name.text}' must be an object literal.`
130
+ )
131
+ continue
132
+ }
133
+ accounts[prop.name.text] = account
134
+ }
135
+
136
+ return Object.keys(accounts).length ? accounts : undefined
137
+ }
138
+
139
+ /**
140
+ * Inspector for `definePersonas()` calls.
141
+ *
142
+ * A persona is entirely declaration — a person, their roles, what they want —
143
+ * so there is no code body and nothing to resolve at runtime. What is read here
144
+ * is the whole thing, and it is generated straight to `personas.gen.json` for
145
+ * the CLI and the console to read without loading the app.
146
+ *
147
+ * `name` is the one field that cannot be defaulted. A persona without one is
148
+ * not a person, and the console has nothing to show.
149
+ */
150
+ export const addPersonas: AddWiring = (logger, node, _checker, state) => {
151
+ if (!ts.isCallExpression(node)) {
152
+ return
153
+ }
154
+
155
+ const expression = node.expression
156
+ if (!ts.isIdentifier(expression) || expression.text !== 'definePersonas') {
157
+ return
158
+ }
159
+
160
+ const firstArg = node.arguments[0]
161
+ if (!firstArg) {
162
+ return
163
+ }
164
+
165
+ const unwrapped = unwrapAs(firstArg)
166
+ if (!ts.isObjectLiteralExpression(unwrapped)) {
167
+ logger.critical(
168
+ ErrorCode.INVALID_VALUE,
169
+ 'definePersonas() needs an inline object — personas are read statically, never evaluated.'
170
+ )
171
+ return
172
+ }
173
+
174
+ const sourceFile = node.getSourceFile().fileName
175
+ const known = Object.keys(DISPOSITIONS)
176
+
177
+ for (const prop of unwrapped.properties) {
178
+ if (!ts.isPropertyAssignment(prop)) {
179
+ continue
180
+ }
181
+
182
+ if (!ts.isIdentifier(prop.name) && !ts.isStringLiteral(prop.name)) {
183
+ logger.critical(
184
+ ErrorCode.NON_LITERAL_WIRE_NAME,
185
+ 'A persona is declared with a key that is not a literal.'
186
+ )
187
+ continue
188
+ }
189
+ const id = prop.name.text
190
+
191
+ if (!ts.isObjectLiteralExpression(prop.initializer)) {
192
+ logger.critical(
193
+ ErrorCode.INVALID_VALUE,
194
+ `Persona '${id}' must be an object literal.`
195
+ )
196
+ continue
197
+ }
198
+ const config = prop.initializer
199
+
200
+ const name = stringProperty(config, 'name')
201
+ if (!name) {
202
+ logger.critical(
203
+ ErrorCode.INVALID_VALUE,
204
+ `Persona '${id}' needs a literal 'name' — a persona is a person, and the console has nothing to show without one.`
205
+ )
206
+ continue
207
+ }
208
+
209
+ // The generated `definePersonas` types this as a union, so a bad value only
210
+ // reaches here in source that does not compile. Caught anyway: a
211
+ // disposition is the entire behaviour, and defaulting a typo to `realistic`
212
+ // would run a different person than the one that was written.
213
+ const disposition = stringProperty(config, 'disposition')
214
+ if (disposition !== undefined && !known.includes(disposition)) {
215
+ logger.critical(
216
+ ErrorCode.INVALID_VALUE,
217
+ `Persona '${id}' has an unknown disposition '${disposition}'. One of: ${known.join(', ')}`
218
+ )
219
+ continue
220
+ }
221
+
222
+ let tuningFailed = false
223
+ const tuning = tuningProperty(config, (message) => {
224
+ tuningFailed = true
225
+ logger.critical(
226
+ ErrorCode.INVALID_VALUE,
227
+ `Persona '${id}': ${message}`
228
+ )
229
+ })
230
+ if (tuningFailed) {
231
+ continue
232
+ }
233
+
234
+ const account = accountProperty(getProperty(config, 'account'))
235
+ const declaredRunnable = booleanProperty(config, 'runnable')
236
+
237
+ const persona: PersonaMeta = {
238
+ id,
239
+ name,
240
+ roles: stringArrayProperty(config, 'roles') ?? [],
241
+ goals: stringArrayProperty(config, 'goals') ?? [],
242
+ tags: stringArrayProperty(config, 'tags') ?? [],
243
+ runnable: isRunnablePersona({
244
+ ...(declaredRunnable !== undefined
245
+ ? { runnable: declaredRunnable }
246
+ : {}),
247
+ ...(account ? { account } : {}),
248
+ }),
249
+ sourceFile,
250
+ }
251
+
252
+ const jobTitle = stringProperty(config, 'jobTitle')
253
+ if (jobTitle !== undefined) persona.jobTitle = jobTitle
254
+ const description = stringProperty(config, 'description')
255
+ if (description !== undefined) persona.description = description
256
+ const personality = stringProperty(config, 'personality')
257
+ if (personality !== undefined) persona.personality = personality
258
+ if (disposition !== undefined) {
259
+ persona.disposition = disposition as PersonaMeta['disposition']
260
+ }
261
+ if (tuning !== undefined) persona.tuning = tuning
262
+ const fixtures = stringArrayProperty(config, 'fixtures')
263
+ if (fixtures !== undefined) persona.fixtures = fixtures
264
+ // Left absent rather than resolved here: "everywhere but production" is a
265
+ // fact about the config, and the inspector has no business baking today's
266
+ // environment list into a meta file that outlives it.
267
+ const environments = stringArrayProperty(config, 'environments')
268
+ if (environments !== undefined) persona.environments = environments
269
+ if (account !== undefined) persona.account = account
270
+ const linkedAccounts = linkedAccountsProperty(config, id, logger)
271
+ if (linkedAccounts !== undefined) persona.linkedAccounts = linkedAccounts
272
+
273
+ state.personas.files.add(sourceFile)
274
+ state.personas.definitions.push(persona)
275
+ }
276
+ }
@@ -30,13 +30,13 @@ const STEPS = [
30
30
  'export const buysAnApple = pikkuScenarioStep({',
31
31
  " name: 'buysAnApple',",
32
32
  " description: 'buys an apple',",
33
- ' browser: true,',
34
- ' func: async ({ logger }, data: { qty: number }) => ({ ok: data.qty > 0 }),',
33
+ ' browser: async ({ logger }, data: { qty: number }) => ({ ok: data.qty > 0 }),',
34
+ ' default: async ({ logger }, data: { qty: number }) => ({ ok: data.qty > 0 }),',
35
35
  '})',
36
36
  'export const seesAReceipt = pikkuScenarioStep({',
37
37
  " name: 'seesAReceipt',",
38
38
  " description: 'sees a receipt',",
39
- ' func: async ({ logger }) => ({ ok: true }),',
39
+ ' default: async ({ logger }) => ({ ok: true }),',
40
40
  '})',
41
41
  ].join('\n')
42
42
 
@@ -209,6 +209,52 @@ describe('pikkuScenarioStep', () => {
209
209
  }
210
210
  })
211
211
 
212
+ test('a scenario that never asserts is a PKU680 critical', async () => {
213
+ const { criticals, cleanup } = await run([
214
+ "await scenario.given('buys an apple', 'buysAnApple', { qty: 1 }, { actor: actors.shopper })",
215
+ "await scenario.when('buys an apple', 'buysAnApple', { qty: 2 }, { actor: actors.shopper })",
216
+ ])
217
+ try {
218
+ assert.ok(
219
+ criticals.find((c) => c.code === 'PKU680'),
220
+ `a flow with no 'then' proves only that nothing threw, and contributes 0/0 to witness coverage, got: ${JSON.stringify(criticals)}`
221
+ )
222
+ } finally {
223
+ await cleanup()
224
+ }
225
+ })
226
+
227
+ test('a scenario with an assertion is not flagged', async () => {
228
+ const { criticals, cleanup } = await run([
229
+ "await scenario.given('buys an apple', 'buysAnApple', { qty: 1 }, { actor: actors.shopper })",
230
+ "await scenario.then('sees a receipt', 'seesAReceipt', {}, { actor: actors.shopper })",
231
+ ])
232
+ try {
233
+ assert.equal(
234
+ criticals.filter((c) => c.code === 'PKU680').length,
235
+ 0,
236
+ `got: ${JSON.stringify(criticals)}`
237
+ )
238
+ } finally {
239
+ await cleanup()
240
+ }
241
+ })
242
+
243
+ test('a scenario driving only rpc steps is not flagged — it has no step ladder to assert on', async () => {
244
+ const { criticals, cleanup } = await run([
245
+ "await scenario.do('reads the order', 'orderGet', { orderId: 'o1' })",
246
+ ])
247
+ try {
248
+ assert.equal(
249
+ criticals.filter((c) => c.code === 'PKU680').length,
250
+ 0,
251
+ `the rule is about scenarios written as given/when/then, got: ${JSON.stringify(criticals)}`
252
+ )
253
+ } finally {
254
+ await cleanup()
255
+ }
256
+ })
257
+
212
258
  test('a non-browser step without an actor is allowed', async () => {
213
259
  const { criticals, cleanup } = await run([
214
260
  "await scenario.given('sees a receipt', 'seesAReceipt', {})",
@@ -39,8 +39,8 @@ describe('addScope inspector', () => {
39
39
  test('extracts a flat scope', async () => {
40
40
  const { state, criticals } = await inspectSource(
41
41
  [
42
- "import { wireScope } from '@pikku/core/scope'",
43
- 'wireScope({',
42
+ "import { defineScope } from '@pikku/core/scope'",
43
+ 'defineScope({',
44
44
  ' admin: {',
45
45
  " displayName: 'Administration',",
46
46
  " description: 'Administrative access',",
@@ -62,8 +62,8 @@ describe('addScope inspector', () => {
62
62
  test('extracts a displayName at every depth', async () => {
63
63
  const { state, criticals } = await inspectSource(
64
64
  [
65
- "import { wireScope } from '@pikku/core/scope'",
66
- 'wireScope({',
65
+ "import { defineScope } from '@pikku/core/scope'",
66
+ 'defineScope({',
67
67
  ' admin: {',
68
68
  " displayName: 'Administration',",
69
69
  ' scopes: {',
@@ -90,8 +90,8 @@ describe('addScope inspector', () => {
90
90
  test('extracts a nested scope tree', async () => {
91
91
  const { state, criticals } = await inspectSource(
92
92
  [
93
- "import { wireScope } from '@pikku/core/scope'",
94
- 'wireScope({',
93
+ "import { defineScope } from '@pikku/core/scope'",
94
+ 'defineScope({',
95
95
  ' admin: {',
96
96
  ' scopes: {',
97
97
  ' invoices: {',
@@ -120,8 +120,8 @@ describe('addScope inspector', () => {
120
120
  test('records the source file', async () => {
121
121
  const { state, file } = await inspectSource(
122
122
  [
123
- "import { wireScope } from '@pikku/core/scope'",
124
- 'wireScope({ admin: {} })',
123
+ "import { defineScope } from '@pikku/core/scope'",
124
+ 'defineScope({ admin: {} })',
125
125
  ].join('\n')
126
126
  )
127
127
 
@@ -134,8 +134,8 @@ describe('addScope inspector', () => {
134
134
  test('extracts several roots from one declaration', async () => {
135
135
  const { state, criticals } = await inspectSource(
136
136
  [
137
- "import { wireScope } from '@pikku/core/scope'",
138
- 'wireScope({ admin: {}, billing: { scopes: { read: {} } } })',
137
+ "import { defineScope } from '@pikku/core/scope'",
138
+ 'defineScope({ admin: {}, billing: { scopes: { read: {} } } })',
139
139
  ].join('\n')
140
140
  )
141
141
 
@@ -150,9 +150,9 @@ describe('addScope inspector', () => {
150
150
  test('extracts several declarations', async () => {
151
151
  const { state } = await inspectSource(
152
152
  [
153
- "import { wireScope } from '@pikku/core/scope'",
154
- 'wireScope({ admin: {} })',
155
- 'wireScope({ billing: {} })',
153
+ "import { defineScope } from '@pikku/core/scope'",
154
+ 'defineScope({ admin: {} })',
155
+ 'defineScope({ billing: {} })',
156
156
  ].join('\n')
157
157
  )
158
158
 
@@ -165,8 +165,8 @@ describe('addScope inspector', () => {
165
165
  test('is critical when a root embeds the separator', async () => {
166
166
  const { criticals } = await inspectSource(
167
167
  [
168
- "import { wireScope } from '@pikku/core/scope'",
169
- "wireScope({ 'admin:users': {} })",
168
+ "import { defineScope } from '@pikku/core/scope'",
169
+ "defineScope({ 'admin:users': {} })",
170
170
  ].join('\n')
171
171
  )
172
172
 
@@ -179,8 +179,8 @@ describe('addScope inspector', () => {
179
179
  test('is critical when a root is the wildcard', async () => {
180
180
  const { criticals } = await inspectSource(
181
181
  [
182
- "import { wireScope } from '@pikku/core/scope'",
183
- "wireScope({ '*': {} })",
182
+ "import { defineScope } from '@pikku/core/scope'",
183
+ "defineScope({ '*': {} })",
184
184
  ].join('\n')
185
185
  )
186
186
 
@@ -193,9 +193,9 @@ describe('addScope inspector', () => {
193
193
  test('is critical when a root key is not a literal', async () => {
194
194
  const { criticals } = await inspectSource(
195
195
  [
196
- "import { wireScope } from '@pikku/core/scope'",
196
+ "import { defineScope } from '@pikku/core/scope'",
197
197
  'const k = String(1)',
198
- 'wireScope({ [k]: {} } as any)',
198
+ 'defineScope({ [k]: {} } as any)',
199
199
  ].join('\n')
200
200
  )
201
201
 
@@ -208,8 +208,8 @@ describe('addScope inspector', () => {
208
208
  test('is critical when a root is not an object literal', async () => {
209
209
  const { criticals } = await inspectSource(
210
210
  [
211
- "import { wireScope } from '@pikku/core/scope'",
212
- "wireScope({ admin: 'nope' } as any)",
211
+ "import { defineScope } from '@pikku/core/scope'",
212
+ "defineScope({ admin: 'nope' } as any)",
213
213
  ].join('\n')
214
214
  )
215
215
 
@@ -223,7 +223,7 @@ describe('addScope inspector', () => {
223
223
  describe('validateScopeReferences', () => {
224
224
  const funcSource = (scopes: string, decls: string[] = []) =>
225
225
  [
226
- "import { wireScope } from '@pikku/core/scope'",
226
+ "import { defineScope } from '@pikku/core/scope'",
227
227
  "import { pikkuSessionlessFunc } from '@pikku/core'",
228
228
  ...decls,
229
229
  'export const f = pikkuSessionlessFunc({',
@@ -235,7 +235,7 @@ describe('validateScopeReferences', () => {
235
235
  test('accepts a declared scope', async () => {
236
236
  const { criticals } = await inspectSource(
237
237
  funcSource("['admin:invoices:create']", [
238
- 'wireScope({',
238
+ 'defineScope({',
239
239
  ' admin: {',
240
240
  ' scopes: { invoices: { scopes: { create: {} } } },',
241
241
  ' },',
@@ -249,7 +249,7 @@ describe('validateScopeReferences', () => {
249
249
  test('accepts an intermediate node of a declared tree', async () => {
250
250
  const { criticals } = await inspectSource(
251
251
  funcSource("['admin:invoices']", [
252
- 'wireScope({',
252
+ 'defineScope({',
253
253
  ' admin: {',
254
254
  ' scopes: { invoices: { scopes: { create: {} } } },',
255
255
  ' },',
@@ -262,7 +262,7 @@ describe('validateScopeReferences', () => {
262
262
 
263
263
  test('rejects an undeclared scope', async () => {
264
264
  const { criticals } = await inspectSource(
265
- funcSource("['billing:read']", ['wireScope({ admin: {} })'])
265
+ funcSource("['billing:read']", ['defineScope({ admin: {} })'])
266
266
  )
267
267
 
268
268
  assert.ok(
@@ -277,7 +277,7 @@ describe('validateScopeReferences', () => {
277
277
 
278
278
  test('lists the available scopes when one is undeclared', async () => {
279
279
  const { criticals } = await inspectSource(
280
- funcSource("['nope']", ['wireScope({ admin: {} })'])
280
+ funcSource("['nope']", ['defineScope({ admin: {} })'])
281
281
  )
282
282
 
283
283
  assert.ok(
@@ -289,7 +289,7 @@ describe('validateScopeReferences', () => {
289
289
  test('rejects a typo in a nested scope', async () => {
290
290
  const { criticals } = await inspectSource(
291
291
  funcSource("['admin:invoice:create']", [
292
- 'wireScope({',
292
+ 'defineScope({',
293
293
  ' admin: {',
294
294
  ' scopes: { invoices: { scopes: { create: {} } } },',
295
295
  ' },',
@@ -305,7 +305,7 @@ describe('validateScopeReferences', () => {
305
305
 
306
306
  test('accepts a wildcard requirement whose node is declared', async () => {
307
307
  const { criticals } = await inspectSource(
308
- funcSource("['admin:*']", ['wireScope({ admin: {} })'])
308
+ funcSource("['admin:*']", ['defineScope({ admin: {} })'])
309
309
  )
310
310
 
311
311
  assert.deepEqual(criticals, [])
@@ -313,7 +313,7 @@ describe('validateScopeReferences', () => {
313
313
 
314
314
  test('rejects a wildcard requirement whose node is undeclared', async () => {
315
315
  const { criticals } = await inspectSource(
316
- funcSource("['billing:*']", ['wireScope({ admin: {} })'])
316
+ funcSource("['billing:*']", ['defineScope({ admin: {} })'])
317
317
  )
318
318
 
319
319
  assert.ok(
@@ -324,7 +324,7 @@ describe('validateScopeReferences', () => {
324
324
 
325
325
  test('rejects a bare wildcard requirement', async () => {
326
326
  const { criticals } = await inspectSource(
327
- funcSource("['*']", ['wireScope({ admin: {} })'])
327
+ funcSource("['*']", ['defineScope({ admin: {} })'])
328
328
  )
329
329
 
330
330
  assert.ok(
@@ -142,7 +142,7 @@ export const addScope: AddWiring = (logger, node, checker, state, _options) => {
142
142
  }
143
143
 
144
144
  const expression = node.expression
145
- if (!ts.isIdentifier(expression) || expression.text !== 'wireScope') {
145
+ if (!ts.isIdentifier(expression) || expression.text !== 'defineScope') {
146
146
  return
147
147
  }
148
148
 
@@ -1,7 +1,7 @@
1
1
  import { createAddKeyedWiring } from './add-keyed-wiring.js'
2
2
 
3
3
  export const addSecret = createAddKeyedWiring({
4
- functionName: 'wireSecret',
4
+ functionName: 'defineSecret',
5
5
  idField: 'secretId',
6
6
  label: 'Secret',
7
7
  schemaPrefix: 'SecretSchema',