@pikku/core 0.12.79 → 0.12.82
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 +473 -0
- package/dist/dev/hot-reload.js +1 -6
- package/dist/ecosystem.d.ts +27 -0
- package/dist/ecosystem.js +26 -0
- package/dist/errors/error-handler.js +4 -2
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/function/function-runner.js +22 -47
- package/dist/function/functions.types.d.ts +9 -5
- package/dist/function/index.d.ts +1 -1
- package/dist/index.d.ts +14 -19
- package/dist/index.js +6 -11
- package/dist/middleware/auth-cookie.d.ts +0 -4
- package/dist/middleware/auth-cookie.js +38 -2
- package/dist/middleware/cors.js +1 -0
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/remote-auth.js +14 -3
- package/dist/permissions.d.ts +8 -10
- package/dist/permissions.js +0 -11
- package/dist/pikku-state.js +16 -5
- package/dist/schema.js +35 -1
- package/dist/services/ai-agent-runner-service.d.ts +7 -0
- package/dist/services/ai-run-state-service.d.ts +17 -1
- package/dist/services/in-memory-ai-run-state-service.d.ts +6 -2
- package/dist/services/in-memory-ai-run-state-service.js +11 -1
- package/dist/services/in-memory-workflow-service.js +2 -0
- package/dist/services/index.d.ts +15 -15
- package/dist/services/index.js +5 -5
- package/dist/services/local-content-request-handler.d.ts +21 -0
- package/dist/services/local-content-request-handler.js +72 -53
- package/dist/services/local-content.d.ts +6 -0
- package/dist/services/local-content.js +14 -1
- package/dist/services/scoped-credential-service.d.ts +21 -0
- package/dist/services/scoped-credential-service.js +53 -0
- package/dist/services/workflow-service.d.ts +4 -2
- package/dist/testing/service-tests/agent-run-service-tests.d.ts +10 -0
- package/dist/testing/service-tests/agent-run-service-tests.js +72 -0
- package/dist/testing/service-tests/ai-storage-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/ai-storage-service-tests.js +302 -0
- package/dist/testing/service-tests/channel-store-tests.d.ts +3 -0
- package/dist/testing/service-tests/channel-store-tests.js +72 -0
- package/dist/testing/service-tests/credential-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/credential-service-tests.js +109 -0
- package/dist/testing/service-tests/deployment-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/deployment-service-tests.js +21 -0
- package/dist/testing/service-tests/event-hub-store-tests.d.ts +3 -0
- package/dist/testing/service-tests/event-hub-store-tests.js +34 -0
- package/dist/testing/service-tests/secret-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/secret-service-tests.js +80 -0
- package/dist/testing/service-tests/session-store-tests.d.ts +3 -0
- package/dist/testing/service-tests/session-store-tests.js +43 -0
- package/dist/testing/service-tests/workflow-run-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/workflow-run-service-tests.js +42 -0
- package/dist/testing/service-tests/workflow-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/workflow-service-tests.js +150 -0
- package/dist/testing/service-tests.d.ts +6 -0
- package/dist/testing/service-tests.js +26 -791
- package/dist/types/core.types.d.ts +11 -3
- package/dist/types/state.types.d.ts +14 -1
- package/dist/wirings/actor-flow/index.d.ts +1 -1
- package/dist/wirings/actor-flow/run-conversation.js +1 -6
- package/dist/wirings/ai-agent/agent-rpc.d.ts +15 -0
- package/dist/wirings/ai-agent/agent-rpc.js +53 -0
- package/dist/wirings/ai-agent/ai-agent-agui.js +1 -5
- package/dist/wirings/ai-agent/ai-agent-finalize.d.ts +58 -0
- package/dist/wirings/ai-agent/ai-agent-finalize.js +138 -0
- package/dist/wirings/ai-agent/ai-agent-interrupt.js +1 -0
- package/dist/wirings/ai-agent/ai-agent-memory.d.ts +2 -8
- package/dist/wirings/ai-agent/ai-agent-memory.js +37 -18
- package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +7 -0
- package/dist/wirings/ai-agent/ai-agent-model-config.js +44 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +5 -9
- package/dist/wirings/ai-agent/ai-agent-runner.js +89 -147
- package/dist/wirings/ai-agent/ai-agent-stream.js +109 -97
- package/dist/wirings/ai-agent/ai-agent-turn.d.ts +57 -0
- package/dist/wirings/ai-agent/ai-agent-turn.js +82 -0
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +47 -2
- package/dist/wirings/ai-agent/index.d.ts +8 -7
- package/dist/wirings/ai-agent/index.js +5 -4
- package/dist/wirings/ai-agent/voice-input.js +1 -6
- package/dist/wirings/ai-agent/voice-output.js +2 -12
- package/dist/wirings/ai-scorer/ai-scorer-grade.d.ts +26 -0
- package/dist/wirings/ai-scorer/ai-scorer-grade.js +33 -0
- package/dist/wirings/ai-scorer/ai-scorer-judge.d.ts +17 -0
- package/dist/wirings/ai-scorer/ai-scorer-judge.js +92 -0
- package/dist/wirings/ai-scorer/ai-scorer-live.d.ts +15 -0
- package/dist/wirings/ai-scorer/ai-scorer-live.js +38 -0
- package/dist/wirings/ai-scorer/ai-scorer-registry.d.ts +18 -0
- package/dist/wirings/ai-scorer/ai-scorer-registry.js +46 -0
- package/dist/wirings/ai-scorer/ai-scorer-sampling.d.ts +8 -0
- package/dist/wirings/ai-scorer/ai-scorer-sampling.js +31 -0
- package/dist/wirings/ai-scorer/ai-scorer-snapshots.d.ts +10 -0
- package/dist/wirings/ai-scorer/ai-scorer-snapshots.js +40 -0
- package/dist/wirings/ai-scorer/ai-scorer-worker.d.ts +15 -0
- package/dist/wirings/ai-scorer/ai-scorer-worker.js +58 -0
- package/dist/wirings/ai-scorer/ai-scorer.d.ts +39 -0
- package/dist/wirings/ai-scorer/ai-scorer.js +40 -0
- package/dist/wirings/ai-scorer/ai-scorer.types.d.ts +90 -0
- package/dist/wirings/ai-scorer/ai-scorer.types.js +4 -0
- package/dist/wirings/ai-scorer/index.d.ts +6 -0
- package/dist/wirings/ai-scorer/index.js +5 -0
- package/dist/wirings/channel/channel-common.js +1 -0
- package/dist/wirings/channel/channel-handler.js +3 -5
- package/dist/wirings/channel/channel-rpc-service.d.ts +0 -6
- package/dist/wirings/channel/channel-rpc-service.js +0 -8
- package/dist/wirings/channel/channel-rpc.types.d.ts +6 -0
- package/dist/wirings/channel/channel-rpc.types.js +8 -0
- package/dist/wirings/channel/channel-runner.d.ts +1 -3
- package/dist/wirings/channel/channel-runner.js +16 -8
- package/dist/wirings/channel/channel.types.d.ts +2 -0
- package/dist/wirings/channel/index.d.ts +5 -6
- package/dist/wirings/channel/index.js +3 -4
- package/dist/wirings/channel/local/local-channel-runner.js +8 -1
- package/dist/wirings/channel/pikku-abstract-channel-handler.js +1 -0
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +3 -0
- package/dist/wirings/cli/channel/cli-channel-runner.js +2 -0
- package/dist/wirings/cli/channel/cli-raw-channel-runner.js +9 -1
- package/dist/wirings/cli/channel/index.d.ts +1 -2
- package/dist/wirings/cli/channel/index.js +0 -1
- package/dist/wirings/cli/cli-runner.js +17 -3
- package/dist/wirings/cli/cli.types.d.ts +0 -8
- package/dist/wirings/cli/command-parser.js +13 -0
- package/dist/wirings/credential/index.d.ts +1 -1
- package/dist/wirings/gateway/gateway-runner.js +9 -2
- package/dist/wirings/gateway/index.d.ts +1 -1
- package/dist/wirings/http/http-routes.js +2 -0
- package/dist/wirings/http/http-runner.d.ts +0 -10
- package/dist/wirings/http/http-runner.js +14 -15
- package/dist/wirings/http/http.types.d.ts +0 -10
- package/dist/wirings/http/index.d.ts +2 -3
- package/dist/wirings/http/index.js +1 -1
- package/dist/wirings/mcp/index.d.ts +1 -1
- package/dist/wirings/mcp/mcp-runner.d.ts +15 -7
- package/dist/wirings/mcp/mcp-runner.js +18 -11
- package/dist/wirings/persona/index.d.ts +3 -4
- package/dist/wirings/persona/index.js +2 -3
- package/dist/wirings/queue/index.d.ts +1 -3
- package/dist/wirings/queue/index.js +1 -3
- package/dist/wirings/rpc/addon-runner.d.ts +4 -0
- package/dist/wirings/rpc/addon-runner.js +19 -3
- package/dist/wirings/rpc/rpc-runner.d.ts +8 -51
- package/dist/wirings/rpc/rpc-runner.js +55 -86
- package/dist/wirings/rpc/rpc-types.d.ts +7 -0
- package/dist/wirings/rpc/wire-addon.d.ts +13 -0
- package/dist/wirings/rpc/wire-addon.js +4 -0
- package/dist/wirings/scheduler/index.d.ts +1 -1
- package/dist/wirings/secret/validate-secret-definitions.js +2 -2
- package/dist/wirings/trigger/index.d.ts +1 -1
- package/dist/wirings/trigger/pikku-trigger-service.d.ts +0 -4
- package/dist/wirings/trigger/trigger-runner.js +1 -0
- package/dist/wirings/virtual-user/index.d.ts +5 -6
- package/dist/wirings/virtual-user/index.js +2 -4
- package/dist/wirings/virtual-user/run-virtual-user.js +11 -11
- package/dist/wirings/virtual-user/virtual-user-derive.js +4 -25
- package/dist/wirings/virtual-user/virtual-user-dispositions.js +1 -4
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +100 -3
- package/dist/wirings/workflow/feature.js +7 -4
- package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -2
- package/dist/wirings/workflow/graph/graph-runner.js +8 -7
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +0 -4
- package/dist/wirings/workflow/index.d.ts +12 -7
- package/dist/wirings/workflow/index.js +7 -3
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +17 -3
- package/dist/wirings/workflow/pikku-scenario-service.js +64 -10
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +28 -147
- package/dist/wirings/workflow/pikku-workflow-service.js +85 -493
- package/dist/wirings/workflow/scenario-step.types.d.ts +8 -0
- package/dist/wirings/workflow/workflow-approval-audit.d.ts +16 -0
- package/dist/wirings/workflow/workflow-approval-audit.js +40 -0
- package/dist/wirings/workflow/workflow-approval-policy.d.ts +20 -0
- package/dist/wirings/workflow/workflow-approval-policy.js +48 -0
- package/dist/wirings/workflow/workflow-approval.d.ts +67 -0
- package/dist/wirings/workflow/workflow-approval.js +177 -0
- package/dist/wirings/workflow/workflow-constants.d.ts +26 -0
- package/dist/wirings/workflow/workflow-constants.js +35 -0
- package/dist/wirings/workflow/workflow-errors.d.ts +58 -0
- package/dist/wirings/workflow/workflow-errors.js +112 -0
- package/dist/wirings/workflow/workflow-meta-resolver.d.ts +11 -0
- package/dist/wirings/workflow/workflow-meta-resolver.js +31 -0
- package/dist/wirings/workflow/workflow-queue-routing.d.ts +8 -0
- package/dist/wirings/workflow/workflow-queue-routing.js +38 -0
- package/dist/wirings/workflow/workflow-queue-wiring.d.ts +20 -0
- package/dist/wirings/workflow/workflow-queue-wiring.js +79 -0
- package/dist/wirings/workflow/workflow-recovery.d.ts +68 -0
- package/dist/wirings/workflow/workflow-recovery.js +101 -0
- package/dist/wirings/workflow/workflow-run-engine.types.d.ts +54 -0
- package/dist/wirings/workflow/workflow-run-ownership.d.ts +17 -0
- package/dist/wirings/workflow/workflow-run-ownership.js +30 -0
- package/dist/wirings/workflow/workflow-suspend.d.ts +12 -0
- package/dist/wirings/workflow/workflow-suspend.js +33 -0
- package/dist/wirings/workflow/workflow.types.d.ts +8 -1
- package/knowledge/decisions/internals/a-non-streaming-agent-run-registers-with-airunstate-too.md +22 -0
- package/knowledge/decisions/internals/a-resumed-agent-turn-is-as-interruptible-as-the-first.md +20 -0
- package/knowledge/decisions/internals/a-scenario-step-template-is-offered-unfilled.md +21 -0
- package/knowledge/decisions/internals/a-virtual-user-decides-whether-to-trust-memory-once-per-turn.md +21 -0
- package/knowledge/decisions/internals/a-wall-clock-threshold-is-a-load-test-in-disguise.md +46 -0
- package/knowledge/decisions/internals/a-workflow-wire-is-built-from-the-run-not-from-the-rpc-service.md +45 -0
- package/knowledge/decisions/internals/addon-pikku-meta-ships-at-the-package-root-or-under-dist.md +32 -0
- package/knowledge/decisions/internals/agent-context-waits-for-a-tool-result-still-being-written.md +21 -0
- package/knowledge/decisions/internals/agent-speech-travels-as-a-custom-agui-event.md +22 -0
- package/knowledge/decisions/internals/an-addon-scope-root-loses-to-a-root-the-host-already-declares.md +39 -0
- package/knowledge/decisions/internals/an-agent-interrupt-is-not-a-failure.md +23 -0
- package/knowledge/decisions/internals/an-agent-run-owned-by-another-instance-says-so.md +25 -0
- package/knowledge/decisions/internals/an-agent-stream-send-must-return-the-inner-sends-promise.md +22 -0
- package/knowledge/decisions/internals/an-empty-text-part-is-omitted-from-an-agent-message.md +20 -0
- package/knowledge/decisions/internals/an-empty-transcript-is-not-recorded.md +22 -0
- package/knowledge/decisions/internals/an-unref-d-timer-cannot-be-awaited-under-node-test.md +66 -0
- package/knowledge/decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md +33 -0
- package/knowledge/decisions/internals/gateway-listener-middleware-runs-without-an-rpc-on-the-wire.md +43 -0
- package/knowledge/decisions/internals/hot-reload-writes-into-the-function-map-captured-at-startup.md +26 -0
- package/knowledge/decisions/internals/index.md +42 -3
- package/knowledge/decisions/internals/only-exposed-functions-enter-a-virtual-user-catalogue.md +21 -0
- package/knowledge/decisions/internals/scenario-given-and-when-are-sugar-but-then-is-not.md +24 -0
- package/knowledge/decisions/internals/side-effects-are-an-allowlist-not-a-boolean.md +34 -0
- package/knowledge/decisions/internals/speech-synthesis-picks-a-voice-per-sentence-and-warns-once.md +24 -0
- package/knowledge/decisions/internals/the-actor-prompt-says-json-because-of-json-object-mode.md +22 -0
- package/knowledge/decisions/internals/the-agent-done-event-goes-through-the-middleware-and-is-awaited.md +26 -0
- package/knowledge/decisions/internals/the-api-report-pins-members-not-just-names.md +43 -0
- package/knowledge/decisions/internals/the-ecosystem-entry-point-carries-the-adapter-surface.md +58 -0
- package/knowledge/decisions/internals/the-middleware-resolution-cache-is-deliberately-unbounded.md +40 -0
- package/knowledge/decisions/internals/the-per-invocation-rpc-view-is-a-class.md +40 -0
- package/knowledge/decisions/internals/the-persona-runtime-is-exported-from-the-persona-entry-point.md +28 -0
- package/knowledge/decisions/internals/the-transcript-event-is-sent-ahead-of-the-run.md +25 -0
- package/knowledge/decisions/internals/the-virtual-user-catalogue-is-the-only-gate-on-what-may-be-called.md +21 -0
- package/knowledge/decisions/internals/the-worker-disposition-is-the-one-that-is-not-testing.md +22 -0
- package/knowledge/decisions/internals/thread-history-records-the-transcript-not-the-audio.md +25 -0
- package/knowledge/decisions/internals/validate-runs-checks-by-precondition.md +115 -0
- package/knowledge/decisions/internals/virtual-user-step-order-comes-from-insertion-order.md +21 -0
- package/knowledge/decisions/internals/voice-output-speaks-unless-voice-input-explicitly-says-otherwise.md +24 -0
- package/knowledge/decisions/internals/wiring-registries-erase-the-generics-their-wire-functions-capture.md +35 -0
- package/knowledge/decisions/security/a-dropped-audit-write-is-always-logged.md +4 -2
- package/knowledge/decisions/security/a-function-never-receives-the-secret-service.md +37 -0
- package/knowledge/decisions/security/a-graph-run-starts-at-an-entry-node-the-graph-declared.md +33 -0
- package/knowledge/decisions/security/a-permission-gets-a-wire-it-cannot-reply-on.md +33 -0
- package/knowledge/decisions/security/a-step-runs-the-function-the-workflow-dispatched-it-with.md +37 -0
- package/knowledge/decisions/security/a-virtual-user-is-never-offered-a-step-that-would-forge-its-own-oracle.md +28 -0
- package/knowledge/decisions/security/a-workflow-run-is-read-and-approved-by-its-owner.md +50 -0
- package/knowledge/decisions/security/an-agent-approval-is-claimed-before-the-tool-runs.md +33 -0
- package/knowledge/decisions/security/an-approval-answer-outlives-the-run-it-answered.md +59 -0
- package/knowledge/decisions/security/an-upload-is-counted-as-it-arrives-not-buffered-then-measured.md +24 -0
- package/knowledge/decisions/security/index.md +9 -0
- package/knowledge/questions/channel-middleware-accepts-bare-factories-that-nothing-resolves.md +35 -0
- package/knowledge/questions/index.md +2 -1
- package/knowledge/questions/unauthorized-channel-replies-escape-the-declared-out-type.md +44 -0
- package/package.json +15 -3
- package/scripts/generate-api-report.d.mts +1 -0
- package/scripts/generate-api-report.mjs +89 -0
- package/scripts/generate-api-report.mts +343 -0
- package/src/api-report.test.ts +69 -0
- package/src/column-form.ts +1 -4
- package/src/crypto-utils.test.ts +15 -2
- package/src/dev/hot-reload.ts +2 -7
- package/src/ecosystem.ts +40 -0
- package/src/errors/error-handler.ts +5 -3
- package/src/errors/error.test.ts +4 -1
- package/src/errors/index.ts +1 -1
- package/src/function/function-runner.test.ts +53 -1
- package/src/function/function-runner.ts +33 -48
- package/src/function/functions.types.ts +15 -9
- package/src/function/index.ts +0 -2
- package/src/handle-error.ts +1 -1
- package/src/index.ts +3 -57
- package/src/middleware/auth-cookie.test.ts +50 -0
- package/src/middleware/auth-cookie.ts +38 -2
- package/src/middleware/cors.ts +2 -1
- package/src/middleware/index.ts +1 -1
- package/src/middleware/remote-auth.test.ts +43 -0
- package/src/middleware/remote-auth.ts +17 -3
- package/src/middleware-runner.ts +4 -2
- package/src/no-any-casts.test.ts +57 -0
- package/src/permissions.test.ts +3 -1
- package/src/permissions.ts +22 -24
- package/src/pikku-state.ts +22 -6
- package/src/public-surface.json +554 -0
- package/src/public-surface.json.README +25 -0
- package/src/public-surface.test.ts +105 -0
- package/src/removed-legacy-exports.test.ts +62 -0
- package/src/schema.test.ts +78 -0
- package/src/schema.ts +36 -1
- package/src/services/ai-agent-runner-service.ts +12 -1
- package/src/services/ai-run-state-service.ts +18 -1
- package/src/services/audit-service.ts +2 -2
- package/src/services/in-memory-ai-run-state-service.ts +16 -2
- package/src/services/in-memory-workflow-service.ts +2 -0
- package/src/services/index.ts +4 -47
- package/src/services/local-content-request-handler.test.ts +43 -5
- package/src/services/local-content-request-handler.ts +103 -74
- package/src/services/local-content.ts +15 -1
- package/src/services/local-email-service.ts +5 -1
- package/src/services/scoped-credential-service.test.ts +86 -0
- package/src/services/scoped-credential-service.ts +63 -0
- package/src/services/system-role-guard.test.ts +4 -1
- package/src/services/workflow-service.ts +9 -2
- package/src/side-effects-are-declared.test.ts +84 -0
- package/src/source-files-stay-composable.test.ts +41 -0
- package/src/testing/service-tests/agent-run-service-tests.ts +98 -0
- package/src/testing/service-tests/ai-storage-service-tests.ts +379 -0
- package/src/testing/service-tests/channel-store-tests.ts +98 -0
- package/src/testing/service-tests/credential-service-tests.ts +143 -0
- package/src/testing/service-tests/deployment-service-tests.ts +33 -0
- package/src/testing/service-tests/event-hub-store-tests.ts +48 -0
- package/src/testing/service-tests/secret-service-tests.ts +105 -0
- package/src/testing/service-tests/session-store-tests.ts +62 -0
- package/src/testing/service-tests/workflow-run-service-tests.ts +59 -0
- package/src/testing/service-tests/workflow-service-tests.ts +308 -0
- package/src/testing/service-tests.ts +31 -1111
- package/src/types/core.types.ts +14 -3
- package/src/types/state.types.ts +17 -1
- package/src/wirings/actor-flow/index.ts +0 -3
- package/src/wirings/actor-flow/run-conversation.ts +1 -6
- package/src/wirings/ai-agent/agent-rpc.ts +120 -0
- package/src/wirings/ai-agent/ai-agent-agui.ts +1 -5
- package/src/wirings/ai-agent/ai-agent-finalize.test.ts +186 -0
- package/src/wirings/ai-agent/ai-agent-finalize.ts +197 -0
- package/src/wirings/ai-agent/ai-agent-interrupt.test.ts +2 -1
- package/src/wirings/ai-agent/ai-agent-interrupt.ts +1 -0
- package/src/wirings/ai-agent/ai-agent-memory.ts +62 -39
- package/src/wirings/ai-agent/ai-agent-model-config.test.ts +72 -3
- package/src/wirings/ai-agent/ai-agent-model-config.ts +49 -1
- package/src/wirings/ai-agent/ai-agent-prepare.ts +12 -12
- package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +1 -0
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +82 -2
- package/src/wirings/ai-agent/ai-agent-runner.ts +120 -160
- package/src/wirings/ai-agent/ai-agent-stream-output-hooks.test.ts +353 -0
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +2 -0
- package/src/wirings/ai-agent/ai-agent-stream.ts +152 -117
- package/src/wirings/ai-agent/ai-agent-thread-ownership.test.ts +1 -1
- package/src/wirings/ai-agent/ai-agent-turn.test.ts +67 -0
- package/src/wirings/ai-agent/ai-agent-turn.ts +122 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +65 -5
- package/src/wirings/ai-agent/index.ts +2 -16
- package/src/wirings/ai-agent/voice-input.ts +1 -6
- package/src/wirings/ai-agent/voice-output.ts +2 -12
- package/src/wirings/ai-scorer/ai-scorer-grade.test.ts +106 -0
- package/src/wirings/ai-scorer/ai-scorer-grade.ts +55 -0
- package/src/wirings/ai-scorer/ai-scorer-judge.test.ts +143 -0
- package/src/wirings/ai-scorer/ai-scorer-judge.ts +120 -0
- package/src/wirings/ai-scorer/ai-scorer-live.test.ts +174 -0
- package/src/wirings/ai-scorer/ai-scorer-live.ts +56 -0
- package/src/wirings/ai-scorer/ai-scorer-registry.ts +63 -0
- package/src/wirings/ai-scorer/ai-scorer-sampling.test.ts +34 -0
- package/src/wirings/ai-scorer/ai-scorer-sampling.ts +36 -0
- package/src/wirings/ai-scorer/ai-scorer-snapshots.test.ts +49 -0
- package/src/wirings/ai-scorer/ai-scorer-snapshots.ts +46 -0
- package/src/wirings/ai-scorer/ai-scorer-worker.test.ts +122 -0
- package/src/wirings/ai-scorer/ai-scorer-worker.ts +69 -0
- package/src/wirings/ai-scorer/ai-scorer.ts +76 -0
- package/src/wirings/ai-scorer/ai-scorer.types.ts +107 -0
- package/src/wirings/ai-scorer/index.ts +24 -0
- package/src/wirings/channel/channel-common.ts +5 -2
- package/src/wirings/channel/channel-handler-shapes.test.ts +72 -0
- package/src/wirings/channel/channel-handler.ts +7 -7
- package/src/wirings/channel/channel-rpc-service.ts +0 -14
- package/src/wirings/channel/channel-rpc.test.ts +25 -4
- package/src/wirings/channel/channel-rpc.types.ts +14 -0
- package/src/wirings/channel/channel-runner.ts +33 -20
- package/src/wirings/channel/channel.types.ts +2 -0
- package/src/wirings/channel/index.ts +1 -20
- package/src/wirings/channel/local/local-channel-runner.test.ts +68 -0
- package/src/wirings/channel/local/local-channel-runner.ts +9 -2
- package/src/wirings/channel/pikku-abstract-channel-handler.ts +2 -1
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +6 -3
- package/src/wirings/cli/channel/cli-channel-runner.ts +3 -1
- package/src/wirings/cli/channel/cli-raw-channel-runner.test.ts +23 -0
- package/src/wirings/cli/channel/cli-raw-channel-runner.ts +12 -1
- package/src/wirings/cli/channel/cli-raw-client-runner.ts +4 -1
- package/src/wirings/cli/channel/index.ts +0 -7
- package/src/wirings/cli/cli-runner.test.ts +68 -0
- package/src/wirings/cli/cli-runner.ts +25 -5
- package/src/wirings/cli/cli.types.ts +0 -39
- package/src/wirings/cli/command-parser.test.ts +19 -0
- package/src/wirings/cli/command-parser.ts +17 -1
- package/src/wirings/credential/index.ts +0 -1
- package/src/wirings/gateway/gateway-channel-meta.test.ts +44 -0
- package/src/wirings/gateway/gateway-runner.ts +27 -19
- package/src/wirings/gateway/index.ts +0 -3
- package/src/wirings/http/http-routes.ts +4 -1
- package/src/wirings/http/http-runner.test.ts +66 -0
- package/src/wirings/http/http-runner.ts +21 -28
- package/src/wirings/http/http.types.ts +0 -15
- package/src/wirings/http/index.ts +2 -8
- package/src/wirings/http/pikku-fetch-http-request.ts +2 -2
- package/src/wirings/http/web-request.ts +1 -1
- package/src/wirings/mcp/index.ts +0 -1
- package/src/wirings/mcp/mcp-runner.test.ts +181 -0
- package/src/wirings/mcp/mcp-runner.ts +37 -21
- package/src/wirings/persona/index.ts +0 -8
- package/src/wirings/persona/persona-environments.test.ts +14 -3
- package/src/wirings/persona/persona.test.ts +13 -3
- package/src/wirings/persona/validate-personas.ts +5 -1
- package/src/wirings/queue/index.ts +0 -14
- package/src/wirings/queue/queue-runner.ts +1 -1
- package/src/wirings/rpc/addon-auth-tags.test.ts +1 -5
- package/src/wirings/rpc/addon-runner.ts +34 -3
- package/src/wirings/rpc/addon-secrets.test.ts +261 -0
- package/src/wirings/rpc/rpc-runner.test.ts +2 -0
- package/src/wirings/rpc/rpc-runner.ts +60 -136
- package/src/wirings/rpc/rpc-types.ts +11 -0
- package/src/wirings/rpc/wire-addon.ts +20 -1
- package/src/wirings/scheduler/index.ts +0 -1
- package/src/wirings/secret/validate-secret-definitions.test.ts +22 -0
- package/src/wirings/secret/validate-secret-definitions.ts +2 -2
- package/src/wirings/trigger/index.ts +0 -1
- package/src/wirings/trigger/pikku-trigger-service.ts +0 -5
- package/src/wirings/trigger/trigger-runner.ts +7 -5
- package/src/wirings/virtual-user/index.ts +0 -16
- package/src/wirings/virtual-user/run-virtual-user.test.ts +18 -9
- package/src/wirings/virtual-user/run-virtual-user.ts +28 -15
- package/src/wirings/virtual-user/virtual-user-agents.test.ts +4 -1
- package/src/wirings/virtual-user/virtual-user-derive.ts +4 -25
- package/src/wirings/virtual-user/virtual-user-dispositions.test.ts +7 -2
- package/src/wirings/virtual-user/virtual-user-dispositions.ts +1 -4
- package/src/wirings/virtual-user/virtual-user-intents.test.ts +13 -3
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +117 -4
- package/src/wirings/workflow/feature.ts +7 -4
- package/src/wirings/workflow/graph/graph-node.ts +1 -1
- package/src/wirings/workflow/graph/graph-runner.test.ts +76 -2
- package/src/wirings/workflow/graph/graph-runner.ts +13 -16
- package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -5
- package/src/wirings/workflow/index.ts +14 -24
- package/src/wirings/workflow/pikku-scenario-service.ts +101 -27
- package/src/wirings/workflow/pikku-workflow-service.test.ts +15 -14
- package/src/wirings/workflow/pikku-workflow-service.ts +214 -743
- package/src/wirings/workflow/scenario-expectations.test.ts +75 -0
- package/src/wirings/workflow/scenario-hooks.test.ts +52 -0
- package/src/wirings/workflow/scenario-step.types.ts +8 -0
- package/src/wirings/workflow/workflow-approval-audit.ts +47 -0
- package/src/wirings/workflow/workflow-approval-policy.test.ts +524 -0
- package/src/wirings/workflow/workflow-approval-policy.ts +68 -0
- package/src/wirings/workflow/workflow-approval.ts +289 -0
- package/src/wirings/workflow/workflow-child-run-session.test.ts +89 -0
- package/src/wirings/workflow/workflow-constants.ts +47 -0
- package/src/wirings/workflow/workflow-dispatch-durability.test.ts +1 -1
- package/src/wirings/workflow/workflow-errors.ts +128 -0
- package/src/wirings/workflow/workflow-inline-authority.test.ts +12 -6
- package/src/wirings/workflow/workflow-meta-resolver.ts +45 -0
- package/src/wirings/workflow/workflow-missing-meta.test.ts +92 -0
- package/src/wirings/workflow/workflow-queue-routing.ts +85 -0
- package/src/wirings/workflow/workflow-queue-wiring.ts +149 -0
- package/src/wirings/workflow/workflow-recovery.ts +162 -0
- package/src/wirings/workflow/workflow-retry-policy.test.ts +1 -1
- package/src/wirings/workflow/workflow-run-authority.test.ts +212 -0
- package/src/wirings/workflow/workflow-run-engine.types.ts +91 -0
- package/src/wirings/workflow/workflow-run-ownership.ts +37 -0
- package/src/wirings/workflow/workflow-suspend.ts +61 -0
- package/src/wirings/workflow/workflow.types.ts +7 -9
- package/src/wirings-stay-decoupled.test.ts +127 -0
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/internal.d.ts +0 -3
- package/dist/internal.js +0 -2
- package/dist/middleware/timeout.d.ts +0 -9
- package/dist/middleware/timeout.js +0 -15
- package/dist/pikku-response.d.ts +0 -6
- package/dist/pikku-response.js +0 -6
- package/dist/services/gopass-secrets.d.ts +0 -15
- package/dist/services/gopass-secrets.js +0 -76
- package/dist/services/http-scenario-actors.d.ts +0 -75
- package/dist/services/http-scenario-actors.js +0 -195
- package/dist/services/http-user-flow-actors.d.ts +0 -67
- package/dist/services/http-user-flow-actors.js +0 -193
- package/dist/services/scenario-actors-service.d.ts +0 -127
- package/dist/services/scenario-actors-service.js +0 -40
- package/dist/services/user-flow-actors-service.d.ts +0 -39
- package/dist/wirings/credential/wire-credential.d.ts +0 -48
- package/dist/wirings/credential/wire-credential.js +0 -47
- package/dist/wirings/oauth2/oauth2-client.d.ts +0 -47
- package/dist/wirings/oauth2/oauth2-client.js +0 -263
- package/dist/wirings/oauth2/oauth2-routes.d.ts +0 -35
- package/dist/wirings/oauth2/oauth2-routes.js +0 -146
- package/dist/wirings/scope/wire-scope.d.ts +0 -33
- package/dist/wirings/scope/wire-scope.js +0 -32
- package/dist/wirings/workflow/dsl/index.d.ts +0 -5
- package/dist/wirings/workflow/dsl/index.js +0 -4
- package/dist/wirings/workflow/graph/index.d.ts +0 -5
- package/dist/wirings/workflow/graph/index.js +0 -4
- package/src/internal.ts +0 -10
- /package/dist/{services/user-flow-actors-service.js → wirings/workflow/workflow-run-engine.types.js} +0 -0
|
@@ -13,8 +13,10 @@ Its `write` discards the event — but before it does, it warns once, and it loo
|
|
|
13
13
|
for a logger in two places: the wire's own, then the singleton passed to the
|
|
14
14
|
constructor.
|
|
15
15
|
|
|
16
|
-
The fallback is the point.
|
|
17
|
-
|
|
16
|
+
The fallback is the point. `wire.logger` is an optional hook for a host that
|
|
17
|
+
wants invocation-scoped logging — core never sets it, so on every path core
|
|
18
|
+
itself drives, the singleton is the only source there is. With only
|
|
19
|
+
`this.wire.logger` the warning would be dropped every time.
|
|
18
20
|
An audit call that silently does nothing is the worst available outcome: the
|
|
19
21
|
function believes it is producing an audit trail, the trail does not exist, and
|
|
20
22
|
nothing anywhere says so. The warning names the function and the fix, and fires
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A function never receives the secret service
|
|
4
|
+
description: Every function-, permission- and auth-facing services type is bounded by SecretlessServices, so reaching for `secrets` in a function body is a type error rather than a lint
|
|
5
|
+
tags: services
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A function never receives the secret service
|
|
9
|
+
|
|
10
|
+
`SecretlessServices<Services>` is `Omit<Services, 'secrets'>`
|
|
11
|
+
(`packages/core/src/types/core.types.ts`), and
|
|
12
|
+
`CoreSecretlessSingletonServices` built from it is the constraint every
|
|
13
|
+
function-, permission- and auth-facing type is bounded by
|
|
14
|
+
(`packages/core/src/function/functions.types.ts`). Destructuring `secrets`
|
|
15
|
+
inside a `pikkuFunc` body does not lint — it does not compile.
|
|
16
|
+
|
|
17
|
+
The rule itself is older than the type: a function holding a `SecretService` can
|
|
18
|
+
read every secret in the vault, which makes its blast radius the whole vault
|
|
19
|
+
rather than the one credential it needs, and makes "which secrets does this
|
|
20
|
+
function depend on?" unanswerable. `[PKU950]` enforces the same confinement for
|
|
21
|
+
a `SecretService` reaching a function under an alias, because a rename does not
|
|
22
|
+
change what it is. Encoding it in the type is what makes the honest mistake
|
|
23
|
+
impossible rather than merely reported: secrets are resolved where things are
|
|
24
|
+
constructed — `pikkuServices`, `pikkuWireServices`, addon service factories,
|
|
25
|
+
middleware — and the function is handed the configured client.
|
|
26
|
+
|
|
27
|
+
The cost is that a function which needs to *ask about* a secret rather than read
|
|
28
|
+
one — "is this key set?", for a readiness or provisioning check — cannot do it
|
|
29
|
+
directly either. It goes through a service that holds `secrets` and exposes only
|
|
30
|
+
that question, which is how `@pikku/addon-console` checks whether an installed
|
|
31
|
+
addon's declared secrets are present.
|
|
32
|
+
|
|
33
|
+
**What this rules out:** widening a function's services type back to
|
|
34
|
+
`CoreServices` for a function that "only needs one secret", and passing the
|
|
35
|
+
secret service through under another name — the type follows the shape and
|
|
36
|
+
`[PKU950]` follows the type. It also rules out treating the absence as an
|
|
37
|
+
oversight to be patched with a cast.
|
package/knowledge/decisions/security/a-graph-run-starts-at-an-entry-node-the-graph-declared.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A graph run starts at an entry node the graph declared
|
|
4
|
+
description: startNode may only name a node in meta.entryNodeIds, and no generated route offers it, because otherwise a caller picks which half of the graph to skip
|
|
5
|
+
tags: workflow
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A graph run starts at an entry node the graph declared
|
|
9
|
+
|
|
10
|
+
`runWorkflowGraph` in
|
|
11
|
+
`packages/core/src/wirings/workflow/graph/graph-runner.ts` takes an optional
|
|
12
|
+
`startNode`. It used to accept any node id the graph contained, validated only
|
|
13
|
+
by `validateGraphReferences` (does the node exist) and `areDependenciesSatisfied`
|
|
14
|
+
(does its input reference another node). A node whose input is a literal or comes
|
|
15
|
+
from the trigger passes both — including a node that is only ever reached after a
|
|
16
|
+
validation, payment or approval node. Choosing it as the start does not skip the
|
|
17
|
+
gate so much as never reach it.
|
|
18
|
+
|
|
19
|
+
`startNode` is now checked against `meta.entryNodeIds`, the set the graph itself
|
|
20
|
+
declared, and the generated `POST /workflow/:workflowName/graph/:nodeId` route
|
|
21
|
+
that offered it to HTTP callers is gone.
|
|
22
|
+
|
|
23
|
+
The parameter stays, because it has a real internal user: `PikkuTriggerService`
|
|
24
|
+
(`packages/core/src/wirings/trigger/pikku-trigger-service.ts`) passes a target's
|
|
25
|
+
`startNode` when a trigger fires. That caller names a node the graph declared, so
|
|
26
|
+
the restriction costs it nothing.
|
|
27
|
+
|
|
28
|
+
**What this rules out:** re-exposing entry-node choice on a generated route, and
|
|
29
|
+
"validating" a `startNode` by existence or by dependency satisfaction — neither
|
|
30
|
+
says the graph meant that node to be an entry point. It does not rule out a graph
|
|
31
|
+
declaring several entry nodes; that is exactly how a graph says which starts are
|
|
32
|
+
legitimate. If a graph needs a start that is not an entry node, the answer is to
|
|
33
|
+
declare it as one, not to widen the check.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A permission gets a wire it cannot reply on
|
|
4
|
+
description: The permission wire is typed with Out = never so a permission cannot send on the channel; that narrowing is not a subtype, so the call site asserts
|
|
5
|
+
tags: core, permissions
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A permission gets a wire it cannot reply on
|
|
9
|
+
|
|
10
|
+
`CorePikkuPermission` in `packages/core/src/function/functions.types.ts` types
|
|
11
|
+
its wire as `PikkuWire<In, never, false, any, PikkuRPC, never, never>`. The
|
|
12
|
+
second parameter is `Out`, and `never` there is deliberate: it makes
|
|
13
|
+
`wire.channel` a `PikkuChannel<unknown, never, …>`, whose `send` accepts
|
|
14
|
+
nothing. A permission is a gate — it answers `true` or `false` — and must not
|
|
15
|
+
be able to write a reply to the caller it is deciding about. A permission that
|
|
16
|
+
could `send` would be able to leak the very data the gate exists to withhold,
|
|
17
|
+
and it would do so before the function it guards has run.
|
|
18
|
+
|
|
19
|
+
`runPikkuFunc` holds an ordinary `PikkuWire`, whose default `Out` is `unknown`.
|
|
20
|
+
`unknown` is not assignable to `never`, and `send` is contravariant in its
|
|
21
|
+
argument, so the narrowing cannot be expressed as a subtype relation — the call
|
|
22
|
+
into `runPermissions` asserts to the exact permission-wire type rather than
|
|
23
|
+
relying on assignability.
|
|
24
|
+
|
|
25
|
+
**What this rules out:** widening `Out` on the permission wire to `unknown` to
|
|
26
|
+
delete the assertion. That silently hands every permission function a working
|
|
27
|
+
`channel.send`. It equally rules out replacing the assertion with `as any`,
|
|
28
|
+
which erases the target type and hides the fact that a specific, intentional
|
|
29
|
+
narrowing is happening.
|
|
30
|
+
|
|
31
|
+
The same reasoning explains the `never` in the fifth and sixth positions
|
|
32
|
+
(`MCPTools`, `IsChannel`): a permission is not an MCP tool host and does not own
|
|
33
|
+
the channel lifecycle.
|
package/knowledge/decisions/security/a-step-runs-the-function-the-workflow-dispatched-it-with.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A step runs the function the workflow dispatched it with
|
|
4
|
+
description: StepState records the step's function name so the worker can reject a queue message naming a different one, because the step executes under the run owner's identity
|
|
5
|
+
tags: workflow
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A step runs the function the workflow dispatched it with
|
|
9
|
+
|
|
10
|
+
`pikkuWorkflowStepWorker` (`workflow-queue-workers.ts`) takes `rpcName` straight
|
|
11
|
+
off the queue message and hands it to `executeWorkflowStep`. That call runs as
|
|
12
|
+
the run's owner — `invokeStepRpc` copies `run.wire.pikkuUserId` onto the wire —
|
|
13
|
+
and `rpcWithWire` does not apply the `expose` gate that the public `/rpc` route
|
|
14
|
+
applies. So the message decided both *what* ran and *as whom*.
|
|
15
|
+
|
|
16
|
+
The claim in `executeWorkflowStepInner` read the step's status and nothing else;
|
|
17
|
+
there was no stored function name to compare against. `StepState` now carries
|
|
18
|
+
`rpcName`, written by `insertStepState` (which already received it) and returned
|
|
19
|
+
by every backend, and the claim rejects a message naming anything else with
|
|
20
|
+
`WorkflowStepFunctionMismatchError` — before any status is mutated, so a forged
|
|
21
|
+
message leaves the run untouched. The graph path takes the same value from
|
|
22
|
+
`nodes[nodeId].rpcName` rather than from the message.
|
|
23
|
+
|
|
24
|
+
`rpcName: undefined` means a store that never recorded one and cannot be
|
|
25
|
+
compared; `null` is a step with no function of its own. Only a recorded value is
|
|
26
|
+
checked.
|
|
27
|
+
|
|
28
|
+
**Reachability, stated plainly:** the step-worker queue is not reachable from the
|
|
29
|
+
public `/rpc/:rpcName` route — the worker is registered without `expose`. The
|
|
30
|
+
exposure this closes is write access to the queue backend, and any in-process
|
|
31
|
+
caller reaching `executeWorkflowStep` directly.
|
|
32
|
+
|
|
33
|
+
**What this rules out:** trusting a queue payload to name the function it
|
|
34
|
+
executes, here or in any future worker, and dropping `rpcName` from `StepState`
|
|
35
|
+
as redundant with the step data — the comparison is the only thing standing
|
|
36
|
+
between queue-write access and running any registered function as the run's
|
|
37
|
+
owner.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A virtual user is never offered a scenario, platform or addon step
|
|
4
|
+
description: Being able to invoke "the webhook arrives" lets the user manufacture the outcome it exists to discover, which invalidates every finding downstream
|
|
5
|
+
tags: core, virtual-user
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A virtual user is never offered a scenario, platform or addon step
|
|
9
|
+
|
|
10
|
+
`deriveVirtualUserCatalogue` excludes scenario bodies and their steps, and
|
|
11
|
+
platform and addon steps, from what a virtual user may call.
|
|
12
|
+
|
|
13
|
+
For scenario bodies the argument is only efficiency: they are held out of every
|
|
14
|
+
deployed unit and are not network-callable, so offering one wastes a turn on a
|
|
15
|
+
404.
|
|
16
|
+
|
|
17
|
+
For a platform or addon step the argument is the oracle itself. A virtual user's
|
|
18
|
+
findings are worth something *because* it cannot manufacture the outcomes it is
|
|
19
|
+
meant to discover. A user that could invoke "Stripe's webhook arrives" forges its
|
|
20
|
+
own payment success, and every finding downstream of that forgery is worthless —
|
|
21
|
+
not merely unreliable, but actively misleading, because it looks like evidence.
|
|
22
|
+
|
|
23
|
+
This is the same class of argument as `allowApprovalRequired` defaulting to
|
|
24
|
+
false, and it is enforced here at derivation rather than left to convention.
|
|
25
|
+
|
|
26
|
+
**What this rules out:** exposing platform steps behind a flag "for
|
|
27
|
+
convenience", or filtering them later in the run loop where a caller could skip
|
|
28
|
+
the filter.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A workflow run is read by its owner, and answered by whoever its gate declares
|
|
4
|
+
description: A run started through a session records that user and only that user may read it; who may answer an approval gate is the gate's own declaration, and a run with no recorded owner has no ownership to enforce
|
|
5
|
+
tags: workflow
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A workflow run is read by its owner, and answered by whoever its gate declares
|
|
9
|
+
|
|
10
|
+
`WorkflowRunWire.pikkuUserId` has always been recorded on every run started
|
|
11
|
+
through a session — `RPCService.startWorkflow` copies it off the wire — and
|
|
12
|
+
nothing read it back. Run ids were the only secret protecting both the status
|
|
13
|
+
routes (which stream `output` and `error`) and `approveStep`, which took no
|
|
14
|
+
session at all and rejected only an already-resolved gate.
|
|
15
|
+
|
|
16
|
+
`assertWorkflowRunOwner`
|
|
17
|
+
(`packages/core/src/wirings/workflow/workflow-run-ownership.ts`) is the check the
|
|
18
|
+
**read** paths share: the generated status routes assert it against the run they
|
|
19
|
+
were already reading.
|
|
20
|
+
|
|
21
|
+
**A run with no recorded owner is not gated.** Triggers, schedulers and routes
|
|
22
|
+
wired without auth start runs with no `pikkuUserId`; there is nobody to compare a
|
|
23
|
+
caller against, and inventing one would reject the framework's own callers rather
|
|
24
|
+
than secure anything. Gate those at the entrypoint with `auth` or `permissions`.
|
|
25
|
+
|
|
26
|
+
This is ownership, not an approver model. It answers "is this your run", not "are
|
|
27
|
+
you entitled to approve this particular gate".
|
|
28
|
+
|
|
29
|
+
## Approving is a separate question, and the gate answers it
|
|
30
|
+
|
|
31
|
+
`approveStep` originally shared `assertWorkflowRunOwner`, which made "only the
|
|
32
|
+
initiator may answer" the one available rule. That is right for "confirm your own
|
|
33
|
+
action" and exactly wrong for four-eyes sign-off, where the initiator is the one
|
|
34
|
+
person who must not sign. Which applies is a property of the decision, so it is
|
|
35
|
+
declared on the gate — `approvers` (`'any' | 'owner' | 'not-initiator'`) and
|
|
36
|
+
`approverScope` on `WorkflowApprovalOptions`.
|
|
37
|
+
|
|
38
|
+
It is enforced wherever the policy is known. Reaching the gate publishes the
|
|
39
|
+
policy into the run state, so a decision submitted after that is judged by the
|
|
40
|
+
approve entrypoint and refused with a 403. A decision can legitimately be
|
|
41
|
+
recorded before the run has reached the gate, and that one has no policy to be
|
|
42
|
+
judged against yet — it is judged on replay instead, alongside payload
|
|
43
|
+
validation, and discarded if it fails.
|
|
44
|
+
|
|
45
|
+
The default is therefore `any`: a gate is a pause for a decision, not an
|
|
46
|
+
authorization boundary, and a route that needs one has `auth`/`permissions`.
|
|
47
|
+
|
|
48
|
+
**What this rules out:** treating a run id as a capability, adding a new run read
|
|
49
|
+
path that does not take a session, and re-deriving who may approve from who
|
|
50
|
+
started the run.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: An agent approval is claimed before the tool runs
|
|
4
|
+
description: resolveApproval is a compare-and-swap returning whether this caller won, because the read that precedes it is not a claim and ten concurrent approvals would otherwise mean ten refunds
|
|
5
|
+
tags: ai-agent
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# An agent approval is claimed before the tool runs
|
|
9
|
+
|
|
10
|
+
Resuming a suspended agent run read the run, checked `status === 'suspended'`,
|
|
11
|
+
snapshotted `pendingApprovals`, called `resolveApproval`, executed the tool, and
|
|
12
|
+
only then wrote `status: 'running'`. Nothing spanned that sequence — no
|
|
13
|
+
transaction, no row lock, no conditional update — so concurrent approvals of the
|
|
14
|
+
same tool call all observed `suspended`, all snapshotted the same list, and all
|
|
15
|
+
reached `execute`. `resolveApproval` returned `void`, so a loser could not even
|
|
16
|
+
tell.
|
|
17
|
+
|
|
18
|
+
`resolveApproval` is now the claim, and returns whether *this* caller made it.
|
|
19
|
+
The stores implement it as a compare-and-swap: Kysely updates the run row only
|
|
20
|
+
while `status = 'suspended'` and `pendingApprovals` still equals the list it
|
|
21
|
+
read; the tool-call stores move the row off `approvalStatus = 'pending'` and
|
|
22
|
+
count the rows they changed. Both resume paths run the tool only for the ids they
|
|
23
|
+
claimed, and a caller that claimed nothing gets an error rather than a silent
|
|
24
|
+
re-run.
|
|
25
|
+
|
|
26
|
+
The claim is per tool call, not per run, so concurrent approvals of *different*
|
|
27
|
+
tool calls on one run all proceed — which is the case that made a run-level
|
|
28
|
+
`claimSuspendedRun` the worse fit.
|
|
29
|
+
|
|
30
|
+
**What this rules out:** treating `getRun` as a claim, adding a resume path that
|
|
31
|
+
executes a tool without a `true` from `resolveApproval`, and implementing
|
|
32
|
+
`resolveApproval` as a read-modify-write in any new store — the return value is
|
|
33
|
+
a promise about atomicity, not a convenience.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: An approval answer outlives the run it answered
|
|
4
|
+
description: Run state holds a decision only while the gate is open, so the settled answer carries decidedBy/decidedAt into the step result and every attempt is written to the audit sink, which has no foreign key to the run
|
|
5
|
+
tags: workflow, audit
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# An approval answer outlives the run it answered
|
|
9
|
+
|
|
10
|
+
An approval gate is asked for so it can be answered for afterwards. "Who
|
|
11
|
+
released the funds" is the entire point of four-eyes sign-off, and until this
|
|
12
|
+
change none of it was kept.
|
|
13
|
+
|
|
14
|
+
Run state is where a decision *waits*, not where it is kept. The record under
|
|
15
|
+
`workflowRuns.state.__approval_<hex>` is overwritten by the next write to that
|
|
16
|
+
key, and cleared outright — `decidedBy: undefined` included — whenever a
|
|
17
|
+
decision is refused on replay. A trail that erases exactly the events worth
|
|
18
|
+
keeping is not a trail.
|
|
19
|
+
|
|
20
|
+
So the answer is recorded twice, in two places with different lifetimes.
|
|
21
|
+
|
|
22
|
+
## The settled decision keeps its provenance
|
|
23
|
+
|
|
24
|
+
`ApprovalOutcome<T>` carries `decidedBy` and `decidedAt` alongside `data`, so
|
|
25
|
+
the answer reaches `workflowStep.result` and, through it,
|
|
26
|
+
`workflowStepHistory` — append-only, and already the per-step event log. Both
|
|
27
|
+
are spread in only when present, so a gate answered without a session keeps the
|
|
28
|
+
shape it had before there was anything to record.
|
|
29
|
+
|
|
30
|
+
## The audit sink is what survives deletion
|
|
31
|
+
|
|
32
|
+
`deleteRun` cascades: `workflowStep` is `onDelete('cascade')` from
|
|
33
|
+
`workflowRuns`, and `workflowStepHistory` from `workflowStep`. Deleting a run
|
|
34
|
+
therefore deletes the sign-off with it — and a *refused* attempt never reaches a
|
|
35
|
+
step at all, so it was never in that trail to begin with.
|
|
36
|
+
|
|
37
|
+
Every answer — accepted, refused at submission, or cleared on replay — is
|
|
38
|
+
written to the `AuditService` as `workflow.approval.decided`, with
|
|
39
|
+
`outcome: 'success' | 'denied'`, the decider under `userIdentity.pikkuUserId`,
|
|
40
|
+
and the run, reason, scopes and refusal in `metadata`. The `audit` table holds
|
|
41
|
+
no foreign key to any workflow table, which is precisely why it is the right
|
|
42
|
+
home.
|
|
43
|
+
|
|
44
|
+
`auditApprovalDecision`
|
|
45
|
+
(`packages/core/src/wirings/workflow/workflow-approval-audit.ts`) is a module of
|
|
46
|
+
its own rather than a method on `PikkuWorkflowService`: the trail is a separate
|
|
47
|
+
concern from running the workflow, and the service is already at the 2000-line
|
|
48
|
+
limit `source-files-stay-composable.test.ts` enforces.
|
|
49
|
+
|
|
50
|
+
**A sink that fails is logged, never thrown.** The trail must not be the reason a
|
|
51
|
+
decision is lost. A project with no `audit` service wired records nothing and is
|
|
52
|
+
otherwise unaffected — the sink is opt-in, and `auditSchema` is deliberately not
|
|
53
|
+
in `pikkuSchemas`.
|
|
54
|
+
|
|
55
|
+
**What this rules out:** a dedicated `workflowApprovals` table. It would have to
|
|
56
|
+
be implemented in all seven backends and would still be deleted with the run
|
|
57
|
+
unless it deliberately broke the cascade — at which point it is the audit table
|
|
58
|
+
with extra steps. If approvals later need to be *queried* as a first-class
|
|
59
|
+
entity rather than read back from the trail, that is when to revisit.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: An upload is counted as it arrives, not buffered and then measured
|
|
4
|
+
description: Reading the whole body before checking its size hands an unauthenticated caller a way to spend the server's memory
|
|
5
|
+
tags: core, content
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# An upload is counted as it arrives, not buffered and then measured
|
|
9
|
+
|
|
10
|
+
The local content request handler accumulates the request body chunk by chunk,
|
|
11
|
+
tracking the running total, and abandons the read the moment it crosses the
|
|
12
|
+
limit. An oversized upload therefore costs the limit, not its own size.
|
|
13
|
+
|
|
14
|
+
The obvious alternative — `await request.arrayBuffer()` and then check
|
|
15
|
+
`byteLength` — has to hold the entire body in memory before it can decide the
|
|
16
|
+
body is too large. For an endpoint reachable before authentication that is a
|
|
17
|
+
way to spend the server's memory for the price of one request, and no size limit
|
|
18
|
+
configured anywhere prevents it.
|
|
19
|
+
|
|
20
|
+
`readRequestBody` in the node HTTP server aborts on the same terms, for the same
|
|
21
|
+
reason.
|
|
22
|
+
|
|
23
|
+
**What this rules out:** replacing the streaming accumulation with a single
|
|
24
|
+
buffered read because the limit check "still happens".
|
|
@@ -10,6 +10,12 @@ A rule about who may do what, and which way it fails when it is unsure.
|
|
|
10
10
|
|
|
11
11
|
<!-- pikku:knowledge-index -->
|
|
12
12
|
- [A dropped audit write is always logged](a-dropped-audit-write-is-always-logged.md) — The no-op audit service falls back to the singleton logger when the wire carries none, so an unconfigured audit call is never silent
|
|
13
|
+
- [A function never receives the secret service](a-function-never-receives-the-secret-service.md) — Every function-, permission- and auth-facing services type is bounded by SecretlessServices, so reaching for `secrets` in a function body is a type error rather than a lint
|
|
14
|
+
- [A graph run starts at an entry node the graph declared](a-graph-run-starts-at-an-entry-node-the-graph-declared.md) — startNode may only name a node in meta.entryNodeIds, and no generated route offers it, because otherwise a caller picks which half of the graph to skip
|
|
15
|
+
- [A permission gets a wire it cannot reply on](a-permission-gets-a-wire-it-cannot-reply-on.md) — The permission wire is typed with Out = never so a permission cannot send on the channel; that narrowing is not a subtype, so the call site asserts
|
|
16
|
+
- [A step runs the function the workflow dispatched it with](a-step-runs-the-function-the-workflow-dispatched-it-with.md) — StepState records the step's function name so the worker can reject a queue message naming a different one, because the step executes under the run owner's identity
|
|
17
|
+
- [A virtual user is never offered a scenario, platform or addon step](a-virtual-user-is-never-offered-a-step-that-would-forge-its-own-oracle.md) — Being able to invoke "the webhook arrives" lets the user manufacture the outcome it exists to discover, which invalidates every finding downstream
|
|
18
|
+
- [A workflow run is read by its owner, and answered by whoever its gate declares](a-workflow-run-is-read-and-approved-by-its-owner.md) — A run started through a session records that user and only that user may read it; who may answer an approval gate is the gate's own declaration, and a run with no recorded owner has no ownership to enforce
|
|
13
19
|
- [An actor's missing approval decision defaults to denied](actor-flow-missing-approval-decisions-default-to-denied.md) — Every pending tool call gets an explicit decision; an id the persona LLM omitted is denied, so a dropped field can never read as consent
|
|
14
20
|
- [Actor sign-in is proven by Set-Cookie, not a non-empty jar](actor-sign-in-is-proven-by-set-cookie-not-a-non-empty-jar.md) — HttpScenarioActor tracks its own signedIn flag and requires the sign-in response itself to set a cookie, because a populated jar proves nothing
|
|
15
21
|
- [Actor sign-in only works for actor-flagged users](actor-sign-in-only-works-for-actor-flagged-users.md) — The scenario actor secret mints sessions for user rows flagged actor and nothing else, so holding it never impersonates a real user
|
|
@@ -24,8 +30,11 @@ A rule about who may do what, and which way it fails when it is unsure.
|
|
|
24
30
|
- [Agent thread ownership fails closed when there is no principal](ai-agent-sessionless-deployments-have-no-thread-ownership.md) — A sessionless caller gets an ephemeral owner and reaches no stored thread, rather than reaching all of them
|
|
25
31
|
- [An agent thread key is always prefixed with the trusted principal](ai-agent-thread-ownership-composes-the-session-principal.md) — Ownership keys are composed as principal:resourceId, so a client id can sub-divide its own boundary but never widen it
|
|
26
32
|
- [Agent tool permission filtering reads the live function config, not the metadata](ai-agent-tool-filtering-reads-the-live-function-config.md) — The pikkuAuth brand survives only on live permission objects, so a metadata-driven check would silently admit every gated tool
|
|
33
|
+
- [An agent approval is claimed before the tool runs](an-agent-approval-is-claimed-before-the-tool-runs.md) — resolveApproval is a compare-and-swap returning whether this caller won, because the read that precedes it is not a claim and ten concurrent approvals would otherwise mean ten refunds
|
|
34
|
+
- [An approval answer outlives the run it answered](an-approval-answer-outlives-the-run-it-answered.md) — Run state holds a decision only while the gate is open, so the settled answer carries decidedBy/decidedAt into the step result and every attempt is written to the audit sink, which has no foreign key to the run
|
|
27
35
|
- [An empty owners constraint matches nothing](an-empty-owners-constraint-matches-nothing.md) — owners is an authorization boundary, so every storage backend must treat [] as no rows rather than no filter
|
|
28
36
|
- [An exposed function with no gate is reported at codegen, not at boot](an-exposed-ungated-function-is-a-codegen-warning.md) — The check runs in the inspector where function meta and every wireAddon declaration are both in hand, because neither source alone can tell a gated function from an ungated one
|
|
37
|
+
- [An upload is counted as it arrives, not buffered and then measured](an-upload-is-counted-as-it-arrives-not-buffered-then-measured.md) — Reading the whole body before checking its size hands an unauthenticated caller a way to spend the server's memory
|
|
29
38
|
- [The console addon's privileged functions gate themselves](console-addon-privileged-functions-gate-themselves.md) — Thread listing is owner-scoped unless the caller holds admin, and addon installation requires an admin session, rather than trusting the host to register a global permission
|
|
30
39
|
- [Core's SSRF guard matches host literals because edge runtimes have no DNS](core-safe-fetch-blocks-ssrf-by-host-literal-not-dns.md) — safeFetch rejects internal address literals and re-validates every redirect hop; it cannot stop DNS rebinding
|
|
31
40
|
- [Core secrets are encrypted with a per-secret DEK wrapped by a KEK](core-secrets-use-a-per-secret-dek-wrapped-by-a-kek.md) — Envelope encryption keeps ciphertext untouched during key rotation, at the cost of storing two blobs per secret
|
package/knowledge/questions/channel-middleware-accepts-bare-factories-that-nothing-resolves.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: question
|
|
3
|
+
title: A channel's middleware list accepts bare factories that nothing ever resolves
|
|
4
|
+
description: CoreChannel.channelMiddleware admits CorePikkuChannelMiddlewareFactory, but no runner calls one, so a bare factory would stall the chain
|
|
5
|
+
tags: core, channel
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A channel's middleware list accepts bare factories that nothing resolves
|
|
9
|
+
|
|
10
|
+
`CoreChannel.channelMiddleware` is typed as
|
|
11
|
+
`(CorePikkuChannelMiddleware | CorePikkuChannelMiddlewareFactory)[]`, but
|
|
12
|
+
`runPikkuFunc`'s `wireChannelMiddleware` parameter admits only
|
|
13
|
+
`CorePikkuChannelMiddleware[]`. The two call sites that bridge them —
|
|
14
|
+
`channel-common.ts` and `channel-handler.ts` — assert across the gap.
|
|
15
|
+
|
|
16
|
+
A factory is `(input: In) => CorePikkuChannelMiddleware`: the author is meant to
|
|
17
|
+
*call* it at wiring time and put the result in the array.
|
|
18
|
+
`combineChannelMiddleware` contains no factory-resolution branch, and neither
|
|
19
|
+
does `combineMiddleware` for ordinary middleware — that is consistent and
|
|
20
|
+
deliberate. So a bare, uncalled factory in `channelMiddleware` would be pushed
|
|
21
|
+
into the chain and then invoked as if it were middleware: it would receive
|
|
22
|
+
`(services, channel, next)` as its single `input` argument, return a middleware
|
|
23
|
+
function that nothing runs, and never call `next` — stalling the chain silently.
|
|
24
|
+
|
|
25
|
+
Nothing in this repository puts a bare factory there, so the failure is
|
|
26
|
+
hypothetical today. The type is what invites it.
|
|
27
|
+
|
|
28
|
+
**What would settle it:** deciding whether `channelMiddleware` should accept
|
|
29
|
+
only resolved middleware — in which case the union is simply wrong and should
|
|
30
|
+
lose its factory arm, and both assertions disappear — or whether the runner
|
|
31
|
+
should resolve factories, in which case `combineChannelMiddleware` needs a
|
|
32
|
+
branch and the ordinary middleware path probably needs the matching one.
|
|
33
|
+
|
|
34
|
+
Until then the assertions name the exact target type rather than `as any`, so
|
|
35
|
+
the gap is visible at both call sites.
|
|
@@ -11,5 +11,6 @@ currently makes a choice that nobody has defended — the choice stays, the doub
|
|
|
11
11
|
gets recorded.
|
|
12
12
|
|
|
13
13
|
<!-- pikku:knowledge-index -->
|
|
14
|
-
|
|
14
|
+
- [A channel's middleware list accepts bare factories that nothing ever resolves](channel-middleware-accepts-bare-factories-that-nothing-resolves.md) — CoreChannel.channelMiddleware admits CorePikkuChannelMiddlewareFactory, but no runner calls one, so a bare factory would stall the chain
|
|
15
|
+
- [An unauthorized channel reply is sent outside the channel's declared Out type](unauthorized-channel-replies-escape-the-declared-out-type.md) — processMessageHandlers sends a bare string on auth failure, which no generated client type admits — nobody has decided what the typed shape should be
|
|
15
16
|
<!-- /pikku:knowledge-index -->
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: question
|
|
3
|
+
title: An unauthorized channel reply is sent outside the channel's declared Out type
|
|
4
|
+
description: processMessageHandlers sends a bare string on auth failure, which no generated client type admits — nobody has decided what the typed shape should be
|
|
5
|
+
tags: core, channel
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# An unauthorized channel reply is sent outside the channel's declared Out type
|
|
9
|
+
|
|
10
|
+
When a message arrives on a channel route that requires a session and none is
|
|
11
|
+
attached, `processMessageHandlers` in
|
|
12
|
+
`packages/core/src/wirings/channel/channel-handler.ts` logs the failure and then
|
|
13
|
+
sends the client a bare string:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
channelHandler.getChannel().send(`Unauthorized for ${routeMessage}`)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
That compiles only because `processMessageHandlers` takes
|
|
20
|
+
`PikkuChannelHandler`, whose generics default to `<unknown, unknown>`, so `send`
|
|
21
|
+
accepts anything at this call site. The channel the application actually
|
|
22
|
+
declared has a concrete `Out`, and the generated client is typed from it. A
|
|
23
|
+
client can therefore receive a value its own types say is impossible, and no
|
|
24
|
+
`@pikku/client-websocket` consumer has a branch for it.
|
|
25
|
+
|
|
26
|
+
The alternatives were never worked through:
|
|
27
|
+
|
|
28
|
+
- **Widen every channel's `Out`** to `Out | ChannelError`, so the error is part
|
|
29
|
+
of the contract. Honest, but it forces a discriminated union on every consumer
|
|
30
|
+
including channels that can never fail auth.
|
|
31
|
+
- **Send on a reserved envelope** the way `channel-rpc` does, and let the client
|
|
32
|
+
library surface it out-of-band rather than as a message. Keeps `Out` clean,
|
|
33
|
+
but adds a second framing that every runtime adapter has to honour.
|
|
34
|
+
- **Send nothing** and let the close code carry it. Simplest, and loses the
|
|
35
|
+
route name that makes the failure debuggable.
|
|
36
|
+
|
|
37
|
+
Until one is chosen the string stays, because dropping it silently is worse: a
|
|
38
|
+
client that is quietly ignored has no way to tell "unauthorized" from "the
|
|
39
|
+
server is slow".
|
|
40
|
+
|
|
41
|
+
**What would settle it:** deciding whether channel-level errors belong in `Out`
|
|
42
|
+
at all, which is the same question `channel-rpc` already answered for RPC frames
|
|
43
|
+
by giving them their own envelope. If the answer is "same as RPC", this becomes
|
|
44
|
+
a decision note and the string becomes an envelope.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/core",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.82",
|
|
4
4
|
"description": "The Pikku runtime — functions, wirings, services, middleware and types",
|
|
5
5
|
"author": "yasser.fadl@gmail.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,8 +15,18 @@
|
|
|
15
15
|
"test": "bash run-tests.sh",
|
|
16
16
|
"test:watch": "bash run-tests.sh --watch",
|
|
17
17
|
"test:coverage": "bash run-tests.sh --coverage",
|
|
18
|
-
"prepublishOnly": "yarn build"
|
|
18
|
+
"prepublishOnly": "yarn build",
|
|
19
|
+
"api-report": "tsx scripts/generate-api-report.mts"
|
|
19
20
|
},
|
|
21
|
+
"sideEffects": [
|
|
22
|
+
"./dist/errors/errors.js",
|
|
23
|
+
"./dist/wirings/rpc/rpc-runner.js",
|
|
24
|
+
"./dist/wirings/rpc/remote-addon-auth.js",
|
|
25
|
+
"./dist/wirings/workflow/workflow-approval-policy.js",
|
|
26
|
+
"./dist/wirings/workflow/workflow-errors.js",
|
|
27
|
+
"./dist/wirings/workflow/workflow-run-ownership.js",
|
|
28
|
+
"./dist/wirings/workflow/pikku-scenario-service.js"
|
|
29
|
+
],
|
|
20
30
|
"exports": {
|
|
21
31
|
".": "./dist/index.js",
|
|
22
32
|
"./middleware": "./dist/middleware/index.js",
|
|
@@ -40,6 +50,7 @@
|
|
|
40
50
|
"./node-host-resolver": "./dist/utils/node-host-resolver.js",
|
|
41
51
|
"./mcp": "./dist/wirings/mcp/index.js",
|
|
42
52
|
"./ai-agent": "./dist/wirings/ai-agent/index.js",
|
|
53
|
+
"./ai-scorer": "./dist/wirings/ai-scorer/index.js",
|
|
43
54
|
"./gateway": "./dist/wirings/gateway/index.js",
|
|
44
55
|
"./cli": "./dist/wirings/cli/index.js",
|
|
45
56
|
"./cli/command-parser": "./dist/wirings/cli/command-parser.js",
|
|
@@ -62,7 +73,8 @@
|
|
|
62
73
|
"./services/temporary-file-service": "./dist/services/temporary-file-service.js",
|
|
63
74
|
"./crypto-utils": "./dist/crypto-utils.js",
|
|
64
75
|
"./hmac": "./dist/utils/hmac.js",
|
|
65
|
-
"./
|
|
76
|
+
"./ecosystem": "./dist/ecosystem.js",
|
|
77
|
+
"./internal": "./dist/ecosystem.js",
|
|
66
78
|
"./schema": "./dist/schema.js",
|
|
67
79
|
"./testing": "./dist/testing/index.js",
|
|
68
80
|
"./dev": "./dist/dev/hot-reload.js"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emits `api-report.md`: every entry point's exported symbols with their full
|
|
3
|
+
* type signatures.
|
|
4
|
+
*
|
|
5
|
+
* `public-surface.json` pins the *names* a consumer can reach. That catches an
|
|
6
|
+
* export appearing or disappearing and nothing else — adding a method to
|
|
7
|
+
* `MetaService`, or making a field on `ChannelMeta` required, sails straight
|
|
8
|
+
* past it. Those are the changes that break a consumer's build, and there are
|
|
9
|
+
* far more of them: 130 exported classes carry 543 members between them, and
|
|
10
|
+
* 268 exported interfaces carry 1,385.
|
|
11
|
+
*
|
|
12
|
+
* So this walks the type checker instead and writes what each symbol actually
|
|
13
|
+
* *is*. The report is committed, and `api-report.test.ts` fails when the two
|
|
14
|
+
* disagree — so a member-level change is a reviewable diff rather than a
|
|
15
|
+
* surprise in someone else's CI.
|
|
16
|
+
*
|
|
17
|
+
* Run: yarn api-report
|
|
18
|
+
*/
|
|
19
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
20
|
+
import { resolve, dirname } from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
import ts from 'typescript';
|
|
23
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
24
|
+
const pkg = JSON.parse(readFileSync(resolve(packageRoot, 'package.json'), 'utf-8'));
|
|
25
|
+
const entryPoints = Object.entries(pkg.exports).map(([subpath, dist]) => ({
|
|
26
|
+
subpath,
|
|
27
|
+
file: resolve(packageRoot, dist.replace('./dist/', './src/').replace(/\.js$/, '.ts')),
|
|
28
|
+
}));
|
|
29
|
+
const program = ts.createProgram(entryPoints.map((e) => e.file), {
|
|
30
|
+
target: ts.ScriptTarget.ESNext,
|
|
31
|
+
module: ts.ModuleKind.Node16,
|
|
32
|
+
moduleResolution: ts.ModuleResolutionKind.Node16,
|
|
33
|
+
strict: true,
|
|
34
|
+
skipLibCheck: true,
|
|
35
|
+
});
|
|
36
|
+
const checker = program.getTypeChecker();
|
|
37
|
+
/** One line per symbol: how it is declared, flattened and stripped of noise. */
|
|
38
|
+
const signature = (exported) => {
|
|
39
|
+
// A re-export is an alias; the declaration that matters is the target's, so
|
|
40
|
+
// an interface re-exported through a barrel still reports its members.
|
|
41
|
+
const symbol = exported.flags & ts.SymbolFlags.Alias
|
|
42
|
+
? checker.getAliasedSymbol(exported)
|
|
43
|
+
: exported;
|
|
44
|
+
const declaration = symbol.declarations?.[0];
|
|
45
|
+
if (!declaration)
|
|
46
|
+
return exported.getName();
|
|
47
|
+
if (ts.isInterfaceDeclaration(declaration) ||
|
|
48
|
+
ts.isClassDeclaration(declaration) ||
|
|
49
|
+
ts.isTypeAliasDeclaration(declaration) ||
|
|
50
|
+
ts.isEnumDeclaration(declaration)) {
|
|
51
|
+
// The declaration itself, so member changes show up in the diff.
|
|
52
|
+
return declaration
|
|
53
|
+
.getText()
|
|
54
|
+
.replace(/\/\*\*[\s\S]*?\*\//g, '')
|
|
55
|
+
.replace(/\/\/[^\n]*/g, '')
|
|
56
|
+
.split('\n')
|
|
57
|
+
.map((l) => l.trim())
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.join(' ');
|
|
60
|
+
}
|
|
61
|
+
const type = checker.getTypeOfSymbolAtLocation(symbol, declaration);
|
|
62
|
+
return `${exported.getName()}: ${checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation)}`;
|
|
63
|
+
};
|
|
64
|
+
const sections = [
|
|
65
|
+
'# @pikku/core API report',
|
|
66
|
+
'',
|
|
67
|
+
'Generated by `yarn api-report`. Every exported symbol with its full',
|
|
68
|
+
'signature, so a member-level change is a reviewable diff. Do not edit.',
|
|
69
|
+
'',
|
|
70
|
+
];
|
|
71
|
+
for (const { subpath, file } of entryPoints) {
|
|
72
|
+
const source = program.getSourceFile(file);
|
|
73
|
+
if (!source)
|
|
74
|
+
continue;
|
|
75
|
+
const moduleSymbol = checker.getSymbolAtLocation(source);
|
|
76
|
+
if (!moduleSymbol)
|
|
77
|
+
continue;
|
|
78
|
+
const exports = checker
|
|
79
|
+
.getExportsOfModule(moduleSymbol)
|
|
80
|
+
.map((s) => [s.getName(), signature(s)])
|
|
81
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
82
|
+
sections.push(`## ${subpath}`, '');
|
|
83
|
+
sections.push('```ts');
|
|
84
|
+
for (const [, sig] of exports)
|
|
85
|
+
sections.push(sig);
|
|
86
|
+
sections.push('```', '');
|
|
87
|
+
}
|
|
88
|
+
writeFileSync(resolve(packageRoot, 'api-report.md'), sections.join('\n'));
|
|
89
|
+
console.log(`api-report.md written for ${entryPoints.length} entry points`);
|