@pikku/inspector 0.12.60 → 0.12.61

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 (136) hide show
  1. package/CHANGELOG.md +301 -0
  2. package/OPTIMIZATION-PLAN.md +19 -19
  3. package/dist/add/add-auth.js +1 -1
  4. package/dist/add/add-channel.d.ts +1 -1
  5. package/dist/add/add-cli.js +5 -0
  6. package/dist/add/add-functions.js +10 -1
  7. package/dist/add/add-http-route.js +18 -18
  8. package/dist/add/add-keyed-wiring.d.ts +2 -0
  9. package/dist/add/add-keyed-wiring.js +3 -1
  10. package/dist/add/add-personas.js +4 -1
  11. package/dist/add/add-secret.js +1 -0
  12. package/dist/add/add-variable.js +1 -0
  13. package/dist/add/add-wire-remote-addon.js +2 -1
  14. package/dist/add/add-workflow-graph.js +63 -6
  15. package/dist/add/add-workflow.d.ts +1 -1
  16. package/dist/error-codes.d.ts +3 -1
  17. package/dist/error-codes.js +3 -1
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.js +1 -0
  20. package/dist/inspector.js +12 -2
  21. package/dist/types.d.ts +25 -21
  22. package/dist/utils/annotate-http-route-auth.js +2 -1
  23. package/dist/utils/check-secret-sinks.js +2 -1
  24. package/dist/utils/compute-required-schemas.d.ts +1 -1
  25. package/dist/utils/contract-hashes.d.ts +2 -2
  26. package/dist/utils/contract-hashes.js +1 -1
  27. package/dist/utils/extract-function-name.js +1 -1
  28. package/dist/utils/extract-services.d.ts +1 -1
  29. package/dist/utils/extract-surface-usage.d.ts +29 -0
  30. package/dist/utils/extract-surface-usage.js +97 -0
  31. package/dist/utils/filter-inspector-state.js +53 -16
  32. package/dist/utils/filter-utils.d.ts +1 -1
  33. package/dist/utils/get-property-value.js +42 -3
  34. package/dist/utils/load-addon-functions-meta.js +31 -0
  35. package/dist/utils/middleware.d.ts +1 -1
  36. package/dist/utils/permissions.d.ts +1 -1
  37. package/dist/utils/post-process.d.ts +27 -7
  38. package/dist/utils/post-process.js +79 -14
  39. package/dist/utils/resolve-deploy-target.d.ts +1 -1
  40. package/dist/utils/resolve-function-meta.d.ts +1 -1
  41. package/dist/utils/resolve-function-types.d.ts +1 -1
  42. package/dist/utils/resolve-versions.js +1 -1
  43. package/dist/utils/schema-generator.d.ts +1 -1
  44. package/dist/utils/serialize-inspector-state.d.ts +2 -1
  45. package/dist/utils/serialize-inspector-state.js +2 -0
  46. package/dist/utils/serialize-openapi-json.d.ts +2 -2
  47. package/dist/utils/validate-auth-sessionless.js +2 -1
  48. package/dist/utils/workflow/derive-workflow-plan.d.ts +1 -1
  49. package/dist/utils/workflow/dsl/extract-dsl-workflow.d.ts +1 -1
  50. package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +1 -1
  51. package/dist/utils/workflow/graph/finalize-workflows.js +1 -1
  52. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +6 -1
  53. package/dist/utils/workflow/graph/serialize-workflow-graph.js +26 -2
  54. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +11 -2
  55. package/package.json +10 -10
  56. package/run-tests.sh +0 -0
  57. package/src/add/add-agent.ts +2 -4
  58. package/src/add/add-auth.ts +2 -2
  59. package/src/add/add-channel.ts +1 -4
  60. package/src/add/add-cli.ts +7 -1
  61. package/src/add/add-credential.ts +3 -6
  62. package/src/add/add-file-with-factory.ts +2 -6
  63. package/src/add/add-functions.ts +24 -31
  64. package/src/add/add-gateway.ts +1 -1
  65. package/src/add/add-http-route-ref.test.ts +149 -0
  66. package/src/add/add-http-route.ts +26 -29
  67. package/src/add/add-keyed-wiring.ts +8 -7
  68. package/src/add/add-permission.ts +1 -1
  69. package/src/add/add-personas.test.ts +26 -2
  70. package/src/add/add-personas.ts +4 -3
  71. package/src/add/add-scenario-step.test.ts +37 -2
  72. package/src/add/add-scope.test.ts +15 -15
  73. package/src/add/add-scope.ts +5 -9
  74. package/src/add/add-secret.ts +1 -0
  75. package/src/add/add-system-role.test.ts +4 -4
  76. package/src/add/add-system-role.ts +2 -4
  77. package/src/add/add-variable.ts +1 -0
  78. package/src/add/add-wire-remote-addon.ts +4 -1
  79. package/src/add/add-workflow-graph-foreach.test.ts +174 -0
  80. package/src/add/add-workflow-graph-input.test.ts +1 -1
  81. package/src/add/add-workflow-graph.ts +78 -6
  82. package/src/add/add-workflow-sleep-expression.test.ts +3 -4
  83. package/src/add/add-workflow.ts +1 -4
  84. package/src/add/addon-contracts.test.ts +1 -1
  85. package/src/add/config-value-metadata.test.ts +94 -0
  86. package/src/add/secret-sink-check.test.ts +4 -1
  87. package/src/error-codes.ts +3 -1
  88. package/src/index.ts +3 -4
  89. package/src/inspector.ts +19 -2
  90. package/src/types.ts +25 -28
  91. package/src/utils/annotate-http-route-auth.test.ts +5 -7
  92. package/src/utils/annotate-http-route-auth.ts +2 -1
  93. package/src/utils/check-secret-sinks.ts +4 -1
  94. package/src/utils/compute-required-schemas.ts +1 -1
  95. package/src/utils/contract-hashes.test.ts +1 -1
  96. package/src/utils/contract-hashes.ts +3 -3
  97. package/src/utils/extract-function-name.ts +1 -1
  98. package/src/utils/extract-services.ts +1 -2
  99. package/src/utils/extract-surface-usage.test.ts +147 -0
  100. package/src/utils/extract-surface-usage.ts +121 -0
  101. package/src/utils/filter-inspector-state.test.ts +138 -12
  102. package/src/utils/filter-inspector-state.ts +82 -21
  103. package/src/utils/filter-utils.ts +1 -1
  104. package/src/utils/get-property-value.ts +43 -5
  105. package/src/utils/load-addon-functions-meta.ts +40 -0
  106. package/src/utils/middleware.ts +1 -1
  107. package/src/utils/permissions.ts +1 -1
  108. package/src/utils/post-process.test.ts +59 -2
  109. package/src/utils/post-process.ts +88 -25
  110. package/src/utils/resolve-deploy-target.ts +1 -1
  111. package/src/utils/resolve-function-meta.ts +1 -4
  112. package/src/utils/resolve-function-types.ts +1 -1
  113. package/src/utils/resolve-versions.ts +2 -5
  114. package/src/utils/schema-generator.ts +3 -3
  115. package/src/utils/serialize-inspector-state.ts +4 -1
  116. package/src/utils/serialize-mcp-json.ts +1 -1
  117. package/src/utils/serialize-openapi-json.ts +2 -2
  118. package/src/utils/validate-auth-sessionless.ts +2 -1
  119. package/src/utils/validate-tags-resolve-to-middleware.test.ts +4 -1
  120. package/src/utils/workflow/derive-workflow-plan.test.ts +1 -1
  121. package/src/utils/workflow/derive-workflow-plan.ts +1 -1
  122. package/src/utils/workflow/dsl/extract-approval-policy.test.ts +1 -1
  123. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +2 -2
  124. package/src/utils/workflow/graph/convert-dsl-to-graph-skip.test.ts +1 -1
  125. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +1 -4
  126. package/src/utils/workflow/graph/finalize-workflow-wires.ts +1 -1
  127. package/src/utils/workflow/graph/finalize-workflows.ts +2 -2
  128. package/src/utils/workflow/graph/serialize-workflow-graph.ts +34 -3
  129. package/src/utils/workflow/graph/workflow-graph.types.ts +12 -2
  130. package/tsconfig.tsbuildinfo +1 -1
  131. package/dist/add/add-ai-agent.d.ts +0 -4
  132. package/dist/add/add-ai-agent.js +0 -442
  133. package/dist/add/add-ai-scorer.d.ts +0 -2
  134. package/dist/add/add-ai-scorer.js +0 -61
  135. package/dist/utils/secret-brokers.d.ts +0 -2
  136. package/dist/utils/secret-brokers.js +0 -9
package/CHANGELOG.md CHANGED
@@ -1,3 +1,304 @@
1
+ ## 0.12.61
2
+
3
+ ### Patch Changes
4
+
5
+ - ab52d8e: Raise an addon's deploy conflict only where the consuming app wires it. A
6
+ function an addon publishes as `deploy: 'serverless'` while naming one of its
7
+ own services serverless-incompatible previously failed the build of any app
8
+ that merely declared the addon, whether or not that function was used.
9
+ - a281de6: A CLI option's type now drives how it is parsed, and it comes from the command function's input schema rather than a second hand-written declaration.
10
+
11
+ `CLIOption` gains `type?: 'string' | 'number' | 'boolean' | 'string[]'` (replacing the never-honoured `array` flag). Declared, it wins over whatever the schema says; left unset, the parser reads the type off the command function's input schema — the same schema the function is validated against — falling back to `default` and then to `'string'`. A `'string[]'` option consumes one token and splits it on commas; every other non-boolean option consumes the next token verbatim, so values that begin with `-` (base64url tokens, negative numbers, dash-leading name patterns) parse correctly. A boolean option is a flag: it consumes the next token only when that token is an explicit literal (`true`/`false`/`1`/`0`/`yes`/`no`), so `--watch false` still turns a default-on flag off instead of leaving `false` behind as a positional.
12
+
13
+ Because the schema is now what types an option, `pikku serve --console --port 4077` no longer reads `--port` as the value of `--console`, and a numeric option arrives as a number instead of a string. An explicit `type` is mostly needed for options that belong to no function input — the `pikku all` filters, which the config factory reads straight off the CLI data, are declared `string[]`, and the CLI's ad-hoc `parseCommaSeparated` normalisation is gone.
14
+
15
+ An array option takes either one comma list or the flag repeated. It never consumes more than one token, because `--tags alpha beta` cannot be told apart from an option followed by a positional; the stray token is reported as an unexpected argument rather than dropped.
16
+
17
+ - 266e3bc: One door per name: `@pikku/core/ecosystem/*` and the package root are gone
18
+
19
+ `@pikku/core` published every module twice. `ecosystem/http` re-exported
20
+ `./http`, `ecosystem/services` re-exported `./services`, and a name was
21
+ reachable through either — so every addition had to be made in two places, and a
22
+ consumer's import said nothing about what it actually used. The package root was
23
+ the same problem at a larger scale: a single barrel of 206 names that no bundler
24
+ could take apart, and the one specifier that revealed nothing at all.
25
+
26
+ Both are deleted. Every name now lives on the subpath that owns it, and every
27
+ import carries that subpath — `@pikku/core/http`, `@pikku/core/services`,
28
+ `@pikku/core/errors`, `@pikku/core/types`.
29
+
30
+ Deleting the facades meant the raw subpaths had to become a superset of them,
31
+ which they were not: the facade tree had accumulated 25 names with no raw home
32
+ and about 26 more filed under a different area than the module they came from.
33
+ Those names moved to the area that owns them, and three areas were published as
34
+ new entry points rather than left on a root that is going away — `./types` (the
35
+ shared type surface, the largest single destination), `./state` and
36
+ `./classification`.
37
+
38
+ `./classification` is one door onto one subject: what a value is and how it must
39
+ be handled. Its three halves would each have been an entry point — the brands
40
+ and manifest types, the stored-form helpers (`hashToken`, `unsafeAsSealed` and
41
+ friends), and `SecretValue` — split by whether a name happens to be a type or a
42
+ value, which is the same defect as the facades. The duration and versioned-id
43
+ helpers went to `./utils`, which already published, and `PikkuRequest` went to
44
+ `./function`: it is the transport-agnostic request base, not an HTTP one — HTTP
45
+ has `PikkuHTTPAbstractRequest`, and the only thing outside core that extends
46
+ `PikkuRequest` is Azure's timer request.
47
+
48
+ `./types` inherited the root barrel's habit before it inherited its contents, so
49
+ the names with an owner elsewhere were moved off it. The middleware types and the
50
+ five middleware factories — `pikkuMiddleware`, `pikkuMiddlewareFactory`,
51
+ `pikkuChannelMiddleware`, `pikkuChannelMiddlewareFactory` and
52
+ `pikkuAgentMiddleware`, runtime values on a types entry point — are now
53
+ `@pikku/core/middleware`; the function meta types are `@pikku/core/function`;
54
+ `SerializedError` is `@pikku/core/errors`; and the generic TypeScript helpers
55
+ (`MakeRequired`, `PickRequired`, `PickOptional`, `RequireAtLeastOne`,
56
+ `JSONPrimitive`, `JSONValue`) are `@pikku/core/utils`. What is left on `./types`
57
+ is the vocabulary the wirings share, which no single module owns.
58
+
59
+ `pikku` was itself a root barrel — `export * from '@pikku/core'` — and
60
+ now exports only the services it bundles.
61
+
62
+ One module survives at the old specifier, and only for the bootstrap:
63
+ `packages/cli` is generated by the _published_ CLI pinned in its `build.sh`, and
64
+ that CLI still writes a bare `@pikku/core` into the files it generates for the
65
+ CLI itself. `bootstrap-compat/root.ts` carries the eight types it names, a test
66
+ in core fails if that list grows, and it goes when the pin moves to a CLI
67
+ released from this branch. The adapter names the pinned CLI reaches for —
68
+ `pikkuState` and `CreateWireServices` — are rewritten to `@pikku/core/state` and
69
+ `@pikku/core/types` by the same `build.sh` patch pass, so no second shim is
70
+ needed for them.
71
+
72
+ A guard test keeps the root shut: it parses imports and rejects a bare
73
+ `@pikku/core` rather than grepping for it, because several tests hold a user's
74
+ file as a template literal, where `import … from '@pikku/core'` is fixture text
75
+ rather than an import this repo makes.
76
+
77
+ An agent scaffold a project generated under an older CLI is refreshed rather
78
+ than left to fail: `pikku all` already deleted one importing an entry point
79
+ `@pikku/core` no longer publishes, and the `#pikku` hub joins that list.
80
+ Without it a project that scaffolds the agent endpoint but
81
+ declares no agents keeps the old file forever — the generator that would rewrite
82
+ it only runs when agents exist, and the file being present is what stops it
83
+ being regenerated as missing.
84
+
85
+ `pikku new addon` also wrote a tsconfig `paths` map naming only the deleted hub.
86
+ An addon's `imports` map points into `dist`, so `paths` is what resolves
87
+ `#pikku/<leaf>` for the addon's own source build — it now names the two leaf
88
+ patterns, in both the addon and its test harness.
89
+
90
+ - 6eef0a0: Bump every dependency to its latest compatible minor/patch across the monorepo.
91
+ - 20d8a39: Drop better-auth's `admin()` plugin.
92
+
93
+ `admin()` bundles three unrelated things: a `role` column, fifteen HTTP
94
+ endpoints authorized against that column, and the enforcement that stops a
95
+ banned user from getting a session. Pikku only ever wanted the third. The
96
+ endpoints were never reachable directly — each sat behind a pikku function with
97
+ its own `admin:users:*` scope — so all the plugin added was a second gate on a
98
+ column that had to be kept in step with the scopes it duplicated. That sync
99
+ (`syncProjectedAdminRole`, run on every authenticated request) is now gone
100
+ along with the column.
101
+
102
+ User administration is implemented against better-auth's internal adapter
103
+ instead, exported from `@pikku/better-auth` so `@pikku/addon-admin` and the
104
+ `scaffold.userAdmin` generator share one implementation:
105
+
106
+ ```ts
107
+ import {
108
+ createAuthUser,
109
+ deleteAuthUser,
110
+ revokeAuthUserSessions,
111
+ setAuthUserBanned,
112
+ setAuthUserPassword,
113
+ } from '@pikku/better-auth'
114
+ ```
115
+
116
+ Ban keeps its schema and its enforcement, in a plugin that does nothing else:
117
+
118
+ ```ts
119
+ import { ban } from '@pikku/better-auth'
120
+
121
+ betterAuth({ plugins: [ban()] })
122
+ ```
123
+
124
+ `ban()` adds `banned`, `banReason` and `banExpires` to `user`, refuses to create
125
+ a session for a banned user, and lapses an expired ban at the sign-in that would
126
+ otherwise be refused. It makes no authorization decision of its own, so it never
127
+ needs to know about scopes.
128
+
129
+ Breaking:
130
+
131
+ - Remove `admin()` from your better-auth `plugins`, and add `ban()` if you ban
132
+ users. `pikku db generate` writes the migration; `user.role` and
133
+ `session.impersonatedBy` are no longer declared by anything.
134
+ - `callAdminApi`, `AdminApiHttpWire`, `syncProjectedAdminRole`,
135
+ `projectedAdminRole` and `ADMIN_ROLE_SCOPES` are removed. `ADMIN_SCOPES`,
136
+ `ADMIN_SCOPE_ROOT` and `ADMIN_SCOPE_TREE` are unchanged.
137
+ - The `scaffold.userAdmin` codegen no longer fails without `admin()`. It now
138
+ fails only when there is no better-auth at all, and warns when `ban()` is
139
+ missing — banning is one capability of six, so the other five still generate.
140
+ - The scope on each function is now the whole authorization decision. Anywhere
141
+ that relied on `user.role` as a backstop no longer has one.
142
+
143
+ - 727671b: Make the public surface an artifact the CLI produces.
144
+
145
+ `@pikku/cli` now ships `surface.json`, computed when the CLI is built by
146
+ generating one application and one addon and reading what each `#pikku/*` leaf
147
+ exports, plus a curated `@pikku/core` entry point for people building on the
148
+ ecosystem. Consumers resolve it as `@pikku/cli/surface.json`.
149
+
150
+ `pikku all` writes the matching per-project overlay to
151
+ `<outDir>/surface-usage.gen.json`: how often each export is imported and which
152
+ source areas it was seen in. The counting happens inside the sweep the inspector
153
+ already makes over every source file, so a prebuild pays no extra pass.
154
+
155
+ - f25f4a2: Fix seven defects found taking one project through `pikku fabric` to deploy
156
+
157
+ **`deploy plan` rewrote the project's own scaffold.** Per-unit codegen re-runs
158
+ `pikku all` with `--outDir` pointed at a unit's `.pikku`, and scaffold imports
159
+ are computed against `config.outDir` — so `console.gen.ts` came back importing
160
+ `../../../../../.deploy/cloudflare/units/<unit>/.pikku/pikku-types.gen.js` and
161
+ the source stopped typechecking until the next ordinary `pikku all`.
162
+
163
+ A guard for this already existed in four generators and had never once fired:
164
+ `LocalVariablesService.get` runs values through `JSON.parse`, so
165
+ `PIKKU_DEPLOY_CODEGEN=1` arrived as the number `1` and every `=== '1'` test
166
+ was false. The comparison is fixed behind a shared `isDeployCodegen`, and the
167
+ real guard now sits in the file writer, which refuses writes _and removals_
168
+ under the scaffold directory for the duration of a per-unit run. Guarding the
169
+ writer rather than each generator matters here: seven further generators had no
170
+ guard at all, several write scaffold source and `.pikku` artifacts in the same
171
+ pass (so an early return would skip too much), and the legacy-scaffold pruners
172
+ delete from the source tree without going through a generator.
173
+
174
+ **`fabric validate` passed on a project that could not deploy.** Deploy clones
175
+ the repository, so a `pikkufabric.config.json` that exists only in the working
176
+ tree is absent exactly when it is needed, and the build container aborts with
177
+ `pikkufabric.config.json not found in repository root`. Validate now reports
178
+ that as an error, and its success line distinguishes "can be linked" from
179
+ "will deploy" instead of reporting unqualified success at a project that is not
180
+ linked yet.
181
+
182
+ **`description` reached `infra.json` as raw source.** `getPropertyValue` fell
183
+ back to `node.getText()`, which is indistinguishable from the value for a lone
184
+ literal — so nobody noticed that a description written as `'a ' + 'b'` arrived
185
+ with the quotes and the `+` still in it, and rendered that way in the console.
186
+ Compile-time constant strings are now folded, checker-free, so a node that
187
+ cannot be resolved statically still takes the old path.
188
+
189
+ **A wired addon that was not installed failed silently.** A missing package
190
+ makes `resolveAddonMeta` return null, which was caught and downgraded to a
191
+ warning; every `ref('<namespace>:…')` then resolved to nothing and the surface
192
+ was dead at runtime with nothing in the build output saying why. The generated
193
+ console is the common case. `wireAddon` now requires its package to be
194
+ installed (`PKU340`), the mirror of the existing `wireRemoteAddon` check whose
195
+ own docs already described this half as if it existed.
196
+
197
+ **The audit-table check demanded an unquoted identifier.** Kysely's schema
198
+ builder always quotes, so `create table "audit"` read as missing on the
199
+ projects most likely to have it. Both this and the better-auth table checks now
200
+ share one matcher that accepts each dialect's quoting — matched pairs only, so
201
+ `"audit'` is not a hit — plus an optional schema qualifier.
202
+
203
+ **Cloudflare bundles kept `pg`.** `getStubModules()` named `postgres` and
204
+ `kysely-postgres-js` but not `pg`, which is the more common driver in
205
+ application code, equally unreachable on a Worker, and additionally pulls at
206
+ `net`/`tls` and `pg-native`, which a Worker build cannot resolve at all.
207
+
208
+ **The deploy plan listed one secret twice.** Two `defineSecret` calls may
209
+ legally share a `secretId` under different local names — the auth scaffold's
210
+ `betterAuthSecret` alongside a hand-written one is the everyday case — and the
211
+ manifest mapped them straight through, so the plan printed two identical
212
+ `create` lines for one resource and `countUnchanged` counted it twice. Secrets
213
+ and variables are now deduplicated in the manifest itself, where variables were
214
+ already being collapsed by accident downstream.
215
+
216
+ - 20d8a39: Generate the schemas a first build used to leave out.
217
+
218
+ A contract type reaches the schema generator only through a file that imports
219
+ it, and for a type exported by nothing but its own function file that file is
220
+ the RPC internal map — which `pikku all` writes _after_ schemas. On a first
221
+ build there is no map to read yet, so those schemas came out missing from a run
222
+ that otherwise succeeded, and the RPC failed with `MissingSchemaError` on its
223
+ first call in a deployment. A second `pikku all` fixed it, which is why this
224
+ only ever bit fresh checkouts and CI.
225
+
226
+ `pikku all` now re-inspects and re-generates schemas when the build that just
227
+ wrote the RPC internal map for the first time left contract references
228
+ unresolved — the condition PKU463 already reports. Confined to that build: a
229
+ project whose references are unresolved for some other reason would otherwise
230
+ pay a second inspection on every run for a re-generation that cannot help it.
231
+
232
+ `@pikku/inspector` exports `unresolvedSchemaReferences(state)`, the check behind
233
+ PKU463, so the decision can be made without re-deriving it.
234
+
235
+ - 3561d67: feat(graph): per-item `forEach` fanout for declarative workflow graphs
236
+
237
+ A graph node can now run once per element of an upstream array:
238
+
239
+ ```ts
240
+ postVideo: {
241
+ forEach: 'getMyVideo', // or (ref) => ref('getMyVideo', 'rows')
242
+ mode: 'sequential', // optional, defaults to 'parallel'
243
+ input: (ref, template, $item) => ({ url: $item('URL VIDEO') }),
244
+ }
245
+ ```
246
+
247
+ Each element runs as its own step instance (`postVideo[0]`, `postVideo[1]`, …)
248
+ and the node's result is the ordered array of per-item results, so a fanned node
249
+ chains straight into another `forEach`. Downstream nodes wait for every item. A
250
+ non-array source fails the run loudly instead of coercing.
251
+
252
+ The change is additive: `forEach` and `mode` are new optional node fields, and
253
+ `$item` is appended after `template` so existing `input: (ref) => …` and
254
+ `input: (ref, template) => …` nodes are unchanged.
255
+
256
+ - a91c433: HTTP routes wired with `ref('ns:fn')` now record the addon function id as their own `pikkuFuncId`, the same way the CLI and channel wirings already do, instead of minting a per-route wrapper function and linking it back through `refTarget`. The `refTarget` field is gone from `HTTPWiringMeta`, and the runtime resolves a namespaced route function against the addon package's own metadata.
257
+ - 2b57ca8: A persona can name the `app` they sign into, and a browser run takes a url per app (`--app-url <app>=<url>`, or `appUrls` on the environment). Each actor's browser context navigates against its own app's base, so a product that is more than one frontend can be proved in one run — including a scenario that crosses from one app to the other. A run whose personas name an app nobody gave a url for is refused rather than browsing the wrong app's pages.
258
+ - 456c88b: Keep an addon function's contract on a route that reaches it through `ref()`, whether or not `.pikku` already exists.
259
+
260
+ The input and output types were read off the type checker, which can only see them once the consumer's own RPC map already lists the addon's functions — that is, on the second run. A first, cold generation widened the route's input to the whole `FlattenedRPCMap`, so the same sources produced a different http-map on CI than on a developer's machine, and a client compiled against the good one failed to build against the other. The addon's own metadata says what the contract is either way, and its published declaration file is where those names live.
261
+
262
+ - 727671b: `wireAddon` and `wireRemoteAddon` move from `#pikku/function` to `#pikku/addon`.
263
+
264
+ Installing an addon and authoring one are the same concept from opposite ends,
265
+ so they are one import: an application's `#pikku/addon` carries the two install
266
+ functions, an addon package's carries `pikkuAddonConfig`, `pikkuAddonServices`,
267
+ `pikkuAddonWireServices` and `AddonBaseServices`.
268
+
269
+ Two generation fixes came with it:
270
+
271
+ - `CredentialsMap` is generated as a type alias rather than an interface. An
272
+ interface has no implicit index signature, so it was never assignable to the
273
+ `Record<string, unknown>` that `GetCredential` is constrained by, and every
274
+ generated project reported two errors on its own function types.
275
+ - An unresolved `SingletonServices` type is now `PKU724` instead of a services
276
+ map with no entries in it. Written out, the empty map made every service
277
+ optional and the real failure resurfaced as unrelated "possibly undefined"
278
+ errors in files nobody had touched.
279
+
280
+ - Updated dependencies [7722ceb]
281
+ - Updated dependencies [375c1ff]
282
+ - Updated dependencies [02a70cd]
283
+ - Updated dependencies [aeef159]
284
+ - Updated dependencies [a281de6]
285
+ - Updated dependencies [266e3bc]
286
+ - Updated dependencies [02a70cd]
287
+ - Updated dependencies [786dae5]
288
+ - Updated dependencies [6eef0a0]
289
+ - Updated dependencies [3561d67]
290
+ - Updated dependencies [a91c433]
291
+ - Updated dependencies [02a70cd]
292
+ - Updated dependencies [9537f74]
293
+ - Updated dependencies [2b57ca8]
294
+ - Updated dependencies [266e3bc]
295
+ - Updated dependencies [9fce0f1]
296
+ - Updated dependencies [83683a0]
297
+ - Updated dependencies [456c88b]
298
+ - Updated dependencies [456c88b]
299
+ - Updated dependencies [c127273]
300
+ - @pikku/core@0.12.85
301
+
1
302
  ## 0.12.60
2
303
 
3
304
  ### Patch Changes
@@ -44,25 +44,25 @@ node --import tsx src/benchmark.ts <target-dir> <iterations>
44
44
 
45
45
  **Dispatch map for `visitRoutes`** (all handlers expect CallExpression + Identifier):
46
46
 
47
- | Identifier | Handler |
48
- | ---------------------- | --------------------- |
49
- | `wireHTTP` | `addHTTPRoute` |
50
- | `wireHTTPRoutes` | `addHTTPRoutes` |
51
- | `wireScheduler` | `addSchedule` |
52
- | `wireTrigger` | `addTrigger` |
53
- | `wireTriggerSource` | `addTrigger` |
54
- | `wireQueueWorker` | `addQueueWorker` |
55
- | `wireChannel` | `addChannel` |
56
- | `wireCLI` | `addCLI` |
57
- | `pikkuCLIRender` | `addCLIRenderers` |
58
- | `wireMCPResource` | `addMCPResource` |
59
- | `wireMCPTool` | `addMCPTool` |
60
- | `wireMCPPrompt` | `addMCPPrompt` |
61
- | `wireWorkflowGraph` | `addWorkflowGraph` |
62
- | `defineSecret` | `addSecret` |
63
- | `defineCredential` | `addCredential` |
64
- | `defineScope` | `addScope` |
65
- | `defineVariable` | `addVariable` |
47
+ | Identifier | Handler |
48
+ | ------------------- | ------------------ |
49
+ | `wireHTTP` | `addHTTPRoute` |
50
+ | `wireHTTPRoutes` | `addHTTPRoutes` |
51
+ | `wireScheduler` | `addSchedule` |
52
+ | `wireTrigger` | `addTrigger` |
53
+ | `wireTriggerSource` | `addTrigger` |
54
+ | `wireQueueWorker` | `addQueueWorker` |
55
+ | `wireChannel` | `addChannel` |
56
+ | `wireCLI` | `addCLI` |
57
+ | `pikkuCLIRender` | `addCLIRenderers` |
58
+ | `wireMCPResource` | `addMCPResource` |
59
+ | `wireMCPTool` | `addMCPTool` |
60
+ | `wireMCPPrompt` | `addMCPPrompt` |
61
+ | `wireWorkflowGraph` | `addWorkflowGraph` |
62
+ | `defineSecret` | `addSecret` |
63
+ | `defineCredential` | `addCredential` |
64
+ | `defineScope` | `addScope` |
65
+ | `defineVariable` | `addVariable` |
66
66
 
67
67
  `addAuth` stays outside the map: it matches several identifiers
68
68
  (`betterAuthStatelessSession`, `betterAuthSession`) and gates them on
@@ -62,7 +62,7 @@ const readArrayProp = (obj, name) => {
62
62
  };
63
63
  /**
64
64
  * Read the callee name of a `plugins: [...]` entry. better-auth plugins are
65
- * factory calls (`bearer()`, `twoFactor({ ... })`, `admin()`); the entry's id
65
+ * factory calls (`bearer()`, `twoFactor({ ... })`, `ban()`); the entry's id
66
66
  * is the called function's name. Member-expression callees (`foo.bar()`) and
67
67
  * non-call entries are ignored.
68
68
  */
@@ -1,6 +1,6 @@
1
1
  import * as ts from 'typescript';
2
2
  import { ErrorCode } from '../error-codes.js';
3
- import type { ChannelMeta } from '@pikku/core/ecosystem/channel';
3
+ import type { ChannelMeta } from '@pikku/core/channel';
4
4
  import type { InspectorState, AddWiring } from '../types.js';
5
5
  /**
6
6
  * Build out the nested message-routes by looking up each handler
@@ -392,6 +392,11 @@ function processOptions(logger, node, typeChecker, inspectorState, inspectorOpti
392
392
  option.short = optProp.initializer.text;
393
393
  }
394
394
  break;
395
+ case 'type':
396
+ if (ts.isStringLiteral(optProp.initializer)) {
397
+ option.type = optProp.initializer.text;
398
+ }
399
+ break;
395
400
  case 'default':
396
401
  // Extract default value from expression
397
402
  if (ts.isStringLiteral(optProp.initializer)) {
@@ -3,7 +3,7 @@ import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
3
3
  import { extractFunctionName, funcIdToTypeName, } from '../utils/extract-function-name.js';
4
4
  import { extractFunctionNode } from '../utils/extract-function-node.js';
5
5
  import { extractUsedWires } from '../utils/extract-services.js';
6
- import { formatVersionedId, parseVersionedId, } from '@pikku/core/ecosystem/types';
6
+ import { formatVersionedId, parseVersionedId } from '@pikku/core/utils';
7
7
  /** Binding keys a `pikkuScenarioStep` may declare, in report order. */
8
8
  const SCENARIO_SURFACE_KEYS = [
9
9
  'browser',
@@ -415,6 +415,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
415
415
  let workflowRetries;
416
416
  let workflowTimeout;
417
417
  let scenarioStepSurfaces;
418
+ let scenarioStepRequiresActor;
418
419
  let scenarioStepAddon;
419
420
  let scenarioStepTemplate;
420
421
  let scopes;
@@ -549,6 +550,13 @@ export const addFunctions = (logger, node, checker, state, options) => {
549
550
  scenarioStepSurfaces = declaredSurfaces.length
550
551
  ? declaredSurfaces
551
552
  : undefined;
553
+ // A browser binding cannot be provisioned without a window to open, so it
554
+ // requires an actor whether or not the step said so.
555
+ scenarioStepRequiresActor =
556
+ getPropertyValue(firstArg, 'actor') === true ||
557
+ declaredSurfaces.includes('browser')
558
+ ? true
559
+ : undefined;
552
560
  }
553
561
  scenarioStepTemplate = getPropertyValue(firstArg, 'template');
554
562
  scopes = getArrayPropertyValue(firstArg, 'scopes') ?? undefined;
@@ -992,6 +1000,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
992
1000
  workflowRetries: workflowRetries ?? undefined,
993
1001
  workflowTimeout: workflowTimeout ?? undefined,
994
1002
  scenarioStepSurfaces,
1003
+ scenarioStepRequiresActor,
995
1004
  // `persona` is the default reading of an unmarked step, so it is left off
996
1005
  // rather than written into every step meta a project already has.
997
1006
  scenarioStepKind: scenarioStepKind && scenarioStepKind !== 'persona'
@@ -128,22 +128,27 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
128
128
  const packageName = ts.isIdentifier(funcInitializer)
129
129
  ? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
130
130
  : null;
131
+ // A ref()-wired route points straight at the addon function, the same way the
132
+ // CLI and channel adders record one. The addon's own metadata is the contract
133
+ // — and it is authoritative where the checker is not: `ref('ns:fn')` only
134
+ // types precisely once the consumer's own rpc map already lists the addon's
135
+ // functions, so a cold run inferred the whole `FlattenedRPCMap` and a warm one
136
+ // inferred the real input. The addon's metadata says which it is either way.
137
+ //
138
+ // The route stays the consuming app's all the same, so it records no
139
+ // packageName: that names the package a wire *runs in*, and running this one
140
+ // inside the addon would hand it the addon's middleware and services instead
141
+ // of the app's. `ref` reaches the addon over RPC, which enters it properly.
131
142
  if (refAddonTarget) {
132
143
  const targetMeta = resolveFunctionMeta(state, refAddonTarget);
133
144
  if (!targetMeta) {
134
145
  logger.warn(`Skipping route '${fullRoute}': addon function metadata for '${refAddonTarget}' is not available yet.`);
135
146
  return;
136
147
  }
148
+ funcName = refAddonTarget;
137
149
  }
138
- ensureFunctionMetadata(state, funcName, fullRoute, funcInitializer, checker, extracted.isHelper);
139
- // Propagate sessionless from the addon target so the runtime can correctly
140
- // determine whether a session is required for ref()-based routes.
141
- if (refAddonTarget) {
142
- const targetMeta = resolveFunctionMeta(state, refAddonTarget);
143
- const inlineMeta = state.functions.meta[funcName];
144
- if (targetMeta && inlineMeta && targetMeta.sessionless !== undefined) {
145
- inlineMeta.sessionless = targetMeta.sessionless;
146
- }
150
+ else {
151
+ ensureFunctionMetadata(state, funcName, fullRoute, funcInitializer, checker, extracted.isHelper);
147
152
  }
148
153
  // The route's own `auth`, falling back to the group's. Recorded on the meta
149
154
  // below so an audit can answer "is this route open?" without re-reading the
@@ -175,12 +180,11 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
175
180
  }
176
181
  const input = fnMeta.inputs?.[0] || null;
177
182
  const getRouteInputKeys = () => {
178
- const targetFuncName = refAddonTarget ?? funcName;
179
- const inputTypes = state.typesLookup.get(targetFuncName);
183
+ const inputTypes = state.typesLookup.get(funcName);
180
184
  if (inputTypes && inputTypes.length > 0) {
181
185
  return extractTypeKeys(inputTypes[0]);
182
186
  }
183
- const targetMeta = resolveFunctionMeta(state, targetFuncName);
187
+ const targetMeta = resolveFunctionMeta(state, funcName);
184
188
  if (targetMeta?.inputSchemaName) {
185
189
  const schema = state.schemas[targetMeta.inputSchemaName];
186
190
  const properties = schema?.properties;
@@ -203,7 +207,7 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
203
207
  const missingParams = params.filter((p) => !inputKeys.includes(p));
204
208
  if (missingParams.length > 0) {
205
209
  logger.critical(ErrorCode.ROUTE_PARAM_MISMATCH, `Route '${fullRoute}' has path parameter(s) [${missingParams.join(', ')}] ` +
206
- `not found in function '${refAddonTarget ?? funcName}' input type. ` +
210
+ `not found in function '${funcName}' input type. ` +
207
211
  `Input type has: [${inputKeys.join(', ')}]`);
208
212
  return;
209
213
  }
@@ -213,7 +217,7 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
213
217
  const missingQuery = query.filter((q) => !inputKeys.includes(q));
214
218
  if (missingQuery.length > 0) {
215
219
  logger.critical(ErrorCode.ROUTE_QUERY_MISMATCH, `Route '${fullRoute}' has query parameter(s) [${missingQuery.join(', ')}] ` +
216
- `not found in function '${refAddonTarget ?? funcName}' input type. ` +
220
+ `not found in function '${funcName}' input type. ` +
217
221
  `Input type has: [${inputKeys.join(', ')}]`);
218
222
  return;
219
223
  }
@@ -227,9 +231,6 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
227
231
  // Resolve permissions
228
232
  const permissions = resolvePermissions(state, obj, tags, checker);
229
233
  state.serviceAggregation.usedFunctions.add(funcName);
230
- if (refAddonTarget) {
231
- state.serviceAggregation.usedFunctions.add(refAddonTarget);
232
- }
233
234
  extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
234
235
  extractWireNames(permissions).forEach((name) => state.serviceAggregation.usedPermissions.add(name));
235
236
  // Check for SSE
@@ -240,7 +241,6 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
240
241
  state.http.files.add(sourceFile.fileName);
241
242
  state.http.meta[method][fullRoute] = {
242
243
  pikkuFuncId: funcName,
243
- ...(refAddonTarget && { refTarget: refAddonTarget }),
244
244
  ...(packageName && { packageName }),
245
245
  route: fullRoute,
246
246
  sourceFile: sourceFile.fileName,
@@ -4,6 +4,8 @@ export interface KeyedWiringConfig {
4
4
  idField: string;
5
5
  label: string;
6
6
  schemaPrefix: string;
7
+ /** Boolean property carrying this wiring kind's deploy-gate opt-in/opt-out. */
8
+ flagField: string;
7
9
  getState: (state: InspectorState) => {
8
10
  definitions: any[];
9
11
  files: Set<string>;
@@ -2,7 +2,7 @@ import * as ts from 'typescript';
2
2
  import { getPropertyValue, getArrayPropertyValue, assertStringLiteralProperty, } from '../utils/get-property-value.js';
3
3
  import { ErrorCode } from '../error-codes.js';
4
4
  import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
5
- import { parseDurationString } from '@pikku/core/ecosystem/types';
5
+ import { parseDurationString } from '@pikku/core/utils';
6
6
  export const createAddKeyedWiring = (config) => {
7
7
  return (logger, node, checker, state, _options) => {
8
8
  if (!ts.isCallExpression(node)) {
@@ -29,6 +29,7 @@ export const createAddKeyedWiring = (config) => {
29
29
  const docsUrlValue = getPropertyValue(obj, 'docsUrl');
30
30
  const allowedHostsValue = getArrayPropertyValue(obj, 'allowedHosts');
31
31
  const idValue = getPropertyValue(obj, config.idField);
32
+ const flagValue = getPropertyValue(obj, config.flagField) === true;
32
33
  let schemaVariableName = null;
33
34
  let schemaSourceFile = null;
34
35
  let schemaIdentifier = null;
@@ -107,6 +108,7 @@ export const createAddKeyedWiring = (config) => {
107
108
  docsUrl: docsUrlValue || undefined,
108
109
  allowedHosts: allowedHostsValue || undefined,
109
110
  [config.idField]: idValue,
111
+ [config.flagField]: flagValue || undefined,
110
112
  schema: schemaLookupName,
111
113
  sourceFile,
112
114
  });
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { DISPOSITIONS } from '@pikku/core/ecosystem/virtual-user';
2
+ import { DISPOSITIONS } from '@pikku/core/virtual-user';
3
3
  import { isRunnablePersona } from '@pikku/core/persona';
4
4
  import { ErrorCode } from '../error-codes.js';
5
5
  import { claimSingleDeclaration } from '../utils/single-declaration.js';
@@ -192,6 +192,9 @@ export const addPersonas = (logger, node, _checker, state) => {
192
192
  const personality = stringProperty(config, 'personality');
193
193
  if (personality !== undefined)
194
194
  persona.personality = personality;
195
+ const app = stringProperty(config, 'app');
196
+ if (app !== undefined)
197
+ persona.app = app;
195
198
  if (disposition !== undefined) {
196
199
  persona.disposition = disposition;
197
200
  }
@@ -4,5 +4,6 @@ export const addSecret = createAddKeyedWiring({
4
4
  idField: 'secretId',
5
5
  label: 'Secret',
6
6
  schemaPrefix: 'SecretSchema',
7
+ flagField: 'optional',
7
8
  getState: (state) => state.secrets,
8
9
  });
@@ -4,5 +4,6 @@ export const addVariable = createAddKeyedWiring({
4
4
  idField: 'variableId',
5
5
  label: 'Variable',
6
6
  schemaPrefix: 'VariableSchema',
7
+ flagField: 'required',
7
8
  getState: (state) => state.variables,
8
9
  });
@@ -38,7 +38,8 @@ export function addWireRemoteAddon(node, state, logger) {
38
38
  ts.isObjectLiteralExpression(prop.initializer)) {
39
39
  hasAuth = true;
40
40
  for (const authProp of prop.initializer.properties) {
41
- if (!ts.isPropertyAssignment(authProp) || !ts.isIdentifier(authProp.name))
41
+ if (!ts.isPropertyAssignment(authProp) ||
42
+ !ts.isIdentifier(authProp.name))
42
43
  continue;
43
44
  if (authProp.name.text === 'credentialId' &&
44
45
  ts.isStringLiteral(authProp.initializer)) {