@pikku/core 0.12.141 → 0.12.142
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 +33 -0
- package/dist/errors/errors.d.ts +1 -1
- package/dist/errors/errors.js +1 -1
- package/dist/function/function-meta.types.d.ts +6 -6
- package/dist/function/functions.types.d.ts +2 -2
- package/dist/services/audit-service.d.ts +4 -4
- package/dist/services/feature-flag-service.d.ts +3 -3
- package/dist/services/{http-personas.d.ts → http-virtual-users.d.ts} +25 -47
- package/dist/services/{http-personas.js → http-virtual-users.js} +29 -53
- package/dist/services/index.d.ts +2 -2
- package/dist/services/index.js +1 -1
- package/dist/services/meta-service.d.ts +6 -6
- package/dist/services/meta-service.js +8 -8
- package/dist/{wirings/workflow → services}/scenario-cookie-jar.d.ts +2 -0
- package/dist/{wirings/workflow → services}/scenario-cookie-jar.js +18 -14
- package/dist/services/system-role-guard.d.ts +1 -1
- package/dist/services/virtual-user-sign-in.d.ts +52 -0
- package/dist/services/virtual-user-sign-in.js +80 -0
- package/dist/services/virtual-user-statement.d.ts +31 -0
- package/dist/services/virtual-user-statement.js +24 -0
- package/dist/services/{personas-service.d.ts → virtual-users-service.d.ts} +25 -23
- package/dist/services/{personas-service.js → virtual-users-service.js} +3 -3
- package/dist/types/core.types.d.ts +11 -11
- package/dist/wirings/role/define-system-role.d.ts +1 -1
- package/dist/wirings/role/define-system-role.js +1 -1
- package/dist/wirings/{actor-flow/actor-flow.types.d.ts → virtual-user/conversation/conversation.types.d.ts} +5 -5
- package/dist/wirings/virtual-user/conversation/index.d.ts +2 -0
- package/dist/wirings/virtual-user/conversation/run-conversation.d.ts +36 -0
- package/dist/wirings/{actor-flow → virtual-user/conversation}/run-conversation.js +30 -30
- package/dist/wirings/virtual-user/define-virtual-users.d.ts +32 -0
- package/dist/wirings/{persona/define-personas.js → virtual-user/define-virtual-users.js} +9 -9
- package/dist/wirings/virtual-user/index.d.ts +30 -7
- package/dist/wirings/virtual-user/index.js +23 -4
- package/dist/wirings/virtual-user/prepare-virtual-user-run.d.ts +8 -8
- package/dist/wirings/virtual-user/prepare-virtual-user-run.js +9 -9
- package/dist/wirings/virtual-user/run-virtual-user.d.ts +6 -6
- package/dist/wirings/virtual-user/run-virtual-user.js +10 -10
- package/dist/wirings/{persona/validate-personas.d.ts → virtual-user/validate-virtual-users.d.ts} +11 -11
- package/dist/wirings/{persona/validate-personas.js → virtual-user/validate-virtual-users.js} +24 -24
- package/dist/wirings/virtual-user/virtual-user-agents.d.ts +7 -7
- package/dist/wirings/virtual-user/virtual-user-agents.js +2 -2
- package/dist/wirings/{persona/persona-app-scopes.d.ts → virtual-user/virtual-user-app-scopes.d.ts} +10 -10
- package/dist/wirings/{persona/persona-app-scopes.js → virtual-user/virtual-user-app-scopes.js} +12 -12
- package/dist/wirings/virtual-user/virtual-user-catalogue.d.ts +3 -3
- package/dist/wirings/virtual-user/virtual-user-catalogue.js +2 -2
- package/dist/wirings/{persona/persona.types.d.ts → virtual-user/virtual-user-declaration.types.d.ts} +24 -24
- package/dist/wirings/virtual-user/virtual-user-derive.js +13 -7
- package/dist/wirings/virtual-user/virtual-user-dispositions.d.ts +1 -1
- package/dist/wirings/{persona/persona-email.d.ts → virtual-user/virtual-user-email.d.ts} +11 -11
- package/dist/wirings/{persona/persona-email.js → virtual-user/virtual-user-email.js} +19 -19
- package/dist/wirings/{persona/persona-environments.d.ts → virtual-user/virtual-user-environments.d.ts} +10 -10
- package/dist/wirings/{persona/persona-environments.js → virtual-user/virtual-user-environments.js} +21 -21
- package/dist/wirings/virtual-user/virtual-user-intents.d.ts +3 -3
- package/dist/wirings/virtual-user/virtual-user-intents.js +3 -3
- package/dist/wirings/{persona/persona-mailbox.d.ts → virtual-user/virtual-user-mailbox.d.ts} +4 -4
- package/dist/wirings/virtual-user/virtual-user-run-store.d.ts +4 -4
- package/dist/wirings/virtual-user/virtual-user-scaffold.d.ts +42 -61
- package/dist/wirings/virtual-user/virtual-user-scaffold.js +69 -117
- package/dist/wirings/virtual-user/virtual-user-schedule-store.d.ts +15 -15
- package/dist/wirings/virtual-user/virtual-user-schedule.d.ts +6 -6
- package/dist/wirings/virtual-user/virtual-user-schedule.js +11 -11
- package/dist/wirings/virtual-user/virtual-user-target.d.ts +6 -6
- package/dist/wirings/virtual-user/virtual-user-target.js +5 -5
- package/dist/wirings/virtual-user/virtual-user.types.d.ts +11 -11
- package/dist/wirings/virtual-user/virtual-user.types.js +1 -1
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +9 -9
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +28 -28
- package/dist/wirings/workflow/pikku-scenario-service.js +77 -77
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +2 -2
- package/dist/wirings/workflow/pikku-workflow-service.js +4 -4
- package/dist/wirings/workflow/scenario-prose.d.ts +8 -8
- package/dist/wirings/workflow/scenario-prose.js +12 -8
- package/dist/wirings/workflow/scenario-run.types.d.ts +13 -13
- package/dist/wirings/workflow/scenario-step.types.d.ts +23 -23
- package/dist/wirings/workflow/workflow.types.d.ts +2 -2
- package/knowledge/decisions/index.md +0 -2
- package/knowledge/decisions/internals/a-credential-name-declared-twice-must-agree-on-schema-and-type.md +29 -0
- package/knowledge/decisions/internals/a-grant-can-name-a-scope-declared-without-its-addon.md +29 -0
- package/knowledge/decisions/internals/a-scheduled-task-writes-its-caller-session-before-it-runs.md +22 -0
- package/knowledge/decisions/internals/a-secret-set-in-memory-shadows-the-variable-of-the-same-name.md +28 -0
- package/knowledge/decisions/internals/a-virtual-user-acts-through-invokeraw-so-a-refusal-is-data.md +32 -0
- package/knowledge/decisions/internals/a-virtual-user-cadence-is-a-row-not-a-timer.md +12 -12
- package/knowledge/decisions/internals/a-virtual-user-converse-signs-in-before-its-first-turn.md +24 -0
- package/knowledge/decisions/internals/a-virtual-user-email-is-computed-with-the-run-id-as-a-sub-address.md +34 -0
- package/knowledge/decisions/internals/a-virtual-user-run-is-not-a-workflow-but-it-needs-a-trigger.md +2 -2
- package/knowledge/decisions/internals/an-agent-is-offered-under-its-registry-key-and-only-within-the-virtual-users-scopes.md +27 -0
- package/knowledge/decisions/internals/an-oauth2-credential-implies-an-optional-app-secret.md +26 -0
- package/knowledge/decisions/internals/analytics-identity-resolvers-run-in-declared-order.md +22 -0
- package/knowledge/decisions/internals/feature-flag-declarations-refuse-an-empty-anyof-and-conflicting-scopes.md +22 -0
- package/knowledge/decisions/internals/feature-flags-fail-open-and-never-enforce-access.md +28 -0
- package/knowledge/decisions/internals/flag-sync-rewrites-only-the-declared-text.md +24 -0
- package/knowledge/decisions/internals/in-a-scenario-a-4xx-is-data-not-an-exception.md +3 -3
- package/knowledge/decisions/internals/index.md +46 -31
- package/knowledge/decisions/internals/one-project-shape-check-two-validators.md +10 -10
- package/knowledge/decisions/internals/role-names-cannot-contain-the-scope-separator.md +30 -0
- package/knowledge/decisions/internals/scenario-agent-calls-sign-in-on-401-only.md +23 -19
- package/knowledge/decisions/internals/scenario-steps-return-drained-response-records.md +2 -2
- package/knowledge/decisions/internals/scenarios-live-in-files-named-for-them.md +12 -12
- package/knowledge/decisions/internals/the-core-scope-validator-refuses-separator-wildcard-and-empty-segments.md +22 -0
- package/knowledge/decisions/internals/the-pikku-user-id-resolves-in-a-fixed-order.md +24 -0
- package/knowledge/decisions/internals/{the-actor-prompt-says-json-because-of-json-object-mode.md → the-virtual-user-prompt-says-json-because-of-json-object-mode.md} +4 -4
- package/knowledge/decisions/internals/{the-persona-runtime-is-exported-from-the-persona-entry-point.md → the-virtual-user-runtime-is-exported-from-the-virtual-user-entry-point.md} +8 -8
- package/knowledge/decisions/internals/validate-checks-virtual-users-through-a-shared-module.md +43 -0
- package/knowledge/decisions/internals/{actor-flow-drives-the-target-through-a-transport-seam.md → virtual-user-conversation-drives-the-target-through-a-transport-seam.md} +6 -6
- package/knowledge/decisions/internals/virtual-user-conversation-verdicts-are-llm-self-evaluations.md +25 -0
- package/knowledge/decisions/internals/virtual-user-conversations-seed-a-hidden-kickoff-message.md +23 -0
- package/knowledge/decisions/internals/virtual-users-are-declared-in-one-definevirtualusers-call-per-codebase.md +30 -0
- package/knowledge/decisions/internals/workflow-core-never-imports-a-browser-driver.md +5 -5
- package/knowledge/decisions/internals/workflow-run-capabilities-are-extensions-not-subclasses.md +2 -2
- package/knowledge/decisions/internals/workflow-scenario-assertions-never-retry-and-record-one-step.md +4 -4
- package/knowledge/decisions/internals/workflow-scenario-hooks-are-a-scenario-only-affordance.md +1 -1
- package/knowledge/decisions/internals/workflow-scenario-prose-is-rendered-from-typed-calls-not-parsed-from-english.md +2 -2
- package/knowledge/decisions/internals/workflow-scenario-step-targets-are-string-literals-for-the-inspector.md +1 -1
- package/knowledge/decisions/internals/workflow-suspended-runs-keep-their-in-process-context.md +1 -1
- package/knowledge/decisions/security/a-bearer-token-checked-against-a-secret-reads-it-per-request.md +26 -0
- package/knowledge/decisions/security/a-call-without-a-session-raises-missing-session-not-forbidden.md +20 -0
- package/knowledge/decisions/security/a-caller-cookie-wins-over-the-jar-for-the-same-name.md +23 -0
- package/knowledge/decisions/security/a-channel-lifecycle-acts-as-the-socket-upgrade-session.md +16 -0
- package/knowledge/decisions/security/a-channel-requires-a-session-unless-it-says-auth-false.md +26 -0
- package/knowledge/decisions/security/a-credential-read-path-follows-its-declared-type-not-absence.md +32 -0
- package/knowledge/decisions/security/a-credential-type-is-a-literal-the-inspector-checks-at-declaration.md +27 -0
- package/knowledge/decisions/security/a-first-party-cookie-is-written-only-after-its-purposes-consent.md +33 -0
- package/knowledge/decisions/security/a-readonly-session-reaches-only-readonly-functions.md +25 -0
- package/knowledge/decisions/security/a-scaffold-flag-says-a-surface-exists-not-who-may-call-it.md +10 -5
- package/knowledge/decisions/security/a-scheduled-virtual-user-run-needs-no-admin-at-run-time.md +27 -0
- package/knowledge/decisions/security/a-scoped-credential-view-is-limited-to-declared-names.md +26 -0
- package/knowledge/decisions/security/a-scoped-secret-view-reads-only-declared-keys-and-never-writes.md +25 -0
- package/knowledge/decisions/security/a-secret-is-sent-only-to-the-hosts-it-declares.md +29 -0
- package/knowledge/decisions/security/a-step-can-name-an-identity-no-virtual-user-holds-through-per-call-headers.md +25 -0
- package/knowledge/decisions/security/a-stopped-run-by-id-skips-the-ownership-check.md +24 -0
- package/knowledge/decisions/security/a-vault-secret-redacts-on-serialization-and-throws-on-coercion.md +29 -0
- package/knowledge/decisions/security/a-virtual-user-catalogue-entry-is-filtered-by-scope-and-approval-is-denied-by-default.md +30 -0
- package/knowledge/decisions/security/a-virtual-user-environment-fails-closed.md +38 -0
- package/knowledge/decisions/security/a-virtual-user-never-runs-against-production.md +23 -0
- package/knowledge/decisions/security/a-virtual-user-reads-its-roles-from-the-apps-own-rpc-before-better-auth.md +27 -0
- package/knowledge/decisions/security/a-virtual-user-run-treats-an-unresolved-environment-as-production.md +41 -0
- package/knowledge/decisions/security/a-virtual-user-scopes-are-expanded-from-its-roles-once-at-run-preparation.md +25 -0
- package/knowledge/decisions/security/a-virtual-user-whose-roles-drift-from-the-declaration-refuses-to-run.md +32 -0
- package/knowledge/decisions/security/addon-screen-scopes-hide-and-do-not-enforce.md +20 -0
- package/knowledge/decisions/security/an-addon-header-can-only-narrow-the-session.md +26 -0
- package/knowledge/decisions/security/an-addon-instance-reads-only-the-secrets-and-credentials-it-is-named.md +33 -0
- package/knowledge/decisions/security/an-addon-may-reach-only-itself-and-the-addons-it-uses.md +16 -0
- package/knowledge/decisions/security/an-api-key-is-jwt-decoded-into-the-session.md +26 -0
- package/knowledge/decisions/security/an-external-virtual-user-caller-presents-one-signed-statement.md +38 -0
- package/knowledge/decisions/security/an-inbound-webhook-source-dispatches-only-verified-requests.md +31 -0
- package/knowledge/decisions/security/an-mcp-call-with-no-http-request-stays-anonymous.md +18 -0
- package/knowledge/decisions/security/an-omitted-user-id-addresses-the-platform-credential.md +32 -0
- package/knowledge/decisions/security/analytics-identity-is-stamped-from-the-session-not-the-event.md +24 -0
- package/knowledge/decisions/security/app-access-is-a-scope-tree-synthesised-from-virtual-users.md +26 -0
- package/knowledge/decisions/security/approval-policy-decides-who-answers-a-tool-call.md +27 -0
- package/knowledge/decisions/security/auth-false-on-a-session-function-warns-and-still-requires-one.md +25 -0
- package/knowledge/decisions/security/bearer-token-hashes-are-unpeppered-sha256.md +18 -0
- package/knowledge/decisions/security/consent-is-read-from-a-cookie-and-a-recognised-denial-counts-as-refusal.md +29 -0
- package/knowledge/decisions/security/cors-defaults-to-any-origin-and-refuses-the-wildcard-with-credentials.md +22 -0
- package/knowledge/decisions/security/credential-backends-share-one-conformance-suite-for-per-user-isolation.md +29 -0
- package/knowledge/decisions/security/each-stage-has-one-virtual-user-key-pair.md +27 -0
- package/knowledge/decisions/security/hmac-verification-fails-closed-on-empty-inputs.md +25 -0
- package/knowledge/decisions/security/index.md +88 -24
- package/knowledge/decisions/security/local-content-refuses-private-buckets-under-the-public-segment.md +21 -0
- package/knowledge/decisions/security/mail-is-allowlisted-before-a-virtual-user-run-sees-it.md +38 -0
- package/knowledge/decisions/security/mcp-resources-tools-and-prompts-take-sessionless-functions.md +18 -0
- package/knowledge/decisions/security/origin-locked-routes-match-the-origin-exactly.md +22 -0
- package/knowledge/decisions/security/oss-auth-works-without-pikku-studio-cloud.md +26 -0
- package/knowledge/decisions/security/outbound-webhooks-use-an-allowlist-when-configured.md +24 -0
- package/knowledge/decisions/security/pikku-carries-virtual-user-scopes-as-data-and-the-app-grants-them.md +35 -0
- package/knowledge/decisions/security/scoped-content-access-is-a-root-folder-with-longest-prefix-grants.md +31 -0
- package/knowledge/decisions/security/services-without-secrets-get-a-throwing-secrets-accessor.md +22 -0
- package/knowledge/decisions/security/shared-guards-refuse-to-mutate-or-shadow-a-system-role.md +34 -0
- package/knowledge/decisions/security/signed-uploads-are-verified-by-the-same-check-as-signed-reads.md +26 -0
- package/knowledge/decisions/security/system-role-scopes-must-be-declared-with-define-scope.md +24 -0
- package/knowledge/decisions/security/system-roles-are-additive-and-immutable.md +16 -0
- package/knowledge/decisions/security/the-channel-rpc-does-not-send-the-callers-session.md +21 -0
- package/knowledge/decisions/security/the-remote-jobs-inbox-fails-closed-without-a-secret.md +24 -0
- package/knowledge/decisions/security/the-remote-rpc-gate-fails-closed-and-binds-the-token-to-its-function.md +34 -0
- package/knowledge/decisions/security/the-workflow-status-stream-rechecks-ownership-on-every-poll.md +16 -0
- package/knowledge/decisions/security/two-credentials-on-one-key-must-share-a-read-mode.md +29 -0
- package/knowledge/decisions/security/verified-bearer-claims-are-pass-through-data-that-core-never-fills.md +23 -0
- package/knowledge/decisions/security/virtual-user-catalogue-excludes-the-machinery-that-runs-virtual-users.md +25 -0
- package/knowledge/decisions/security/{actor-flow-missing-approval-decisions-default-to-denied.md → virtual-user-conversation-missing-approval-decisions-default-to-denied.md} +7 -7
- package/knowledge/decisions/security/virtual-user-run-records-are-gated-behind-a-read-scope.md +18 -0
- package/knowledge/decisions/security/virtual-user-sign-in-does-not-exist-in-production.md +25 -0
- package/knowledge/decisions/security/virtual-user-sign-in-has-one-door.md +42 -0
- package/knowledge/decisions/security/{actor-sign-in-is-proven-by-set-cookie-not-a-non-empty-jar.md → virtual-user-sign-in-is-proven-by-set-cookie-not-a-non-empty-jar.md} +10 -8
- package/knowledge/decisions/security/virtual-user-sign-in-only-works-for-virtual-user-flagged-users.md +29 -0
- package/knowledge/decisions/security/workflow-helper-functions-are-sessionless-by-declaration.md +18 -0
- package/knowledge/decisions/security/{workflow-scenario-sessions-are-isolated-per-actor-and-per-scenario.md → workflow-scenario-sessions-are-isolated-per-virtual-user-and-per-scenario.md} +6 -6
- package/knowledge/decisions/security/workflow-scenario-steps-are-never-network-invocable.md +5 -5
- package/knowledge/decisions/security/{workflow-actor-steps-always-use-the-real-transport.md → workflow-virtual-user-steps-always-use-the-real-transport.md} +11 -11
- package/knowledge/decisions/security/writing-a-virtual-user-schedule-is-gated-by-its-own-scope.md +18 -0
- package/knowledge/index.md +0 -2
- package/knowledge/questions/a-bearer-jwt-with-no-identity-becomes-a-session.md +24 -0
- package/knowledge/questions/dev-quick-login-as-impersonation.md +25 -0
- package/knowledge/questions/does-a-delayed-rpc-run-as-the-session-it-was-scheduled-with.md +26 -0
- package/knowledge/questions/does-a-user-id-with-a-colon-read-another-users-credentials.md +27 -0
- package/knowledge/questions/does-a-wirecli-command-need-a-session-by-default.md +26 -0
- package/knowledge/questions/does-an-adversarial-in-scope-success-count-as-a-finding.md +26 -0
- package/knowledge/questions/does-approve-step-need-the-run-owners-session-or-the-gates-approvers.md +34 -0
- package/knowledge/questions/does-cors-origin-true-reflect-any-origin-with-credentials.md +20 -0
- package/knowledge/questions/does-skip-user-session-do-anything-on-a-channel.md +24 -0
- package/knowledge/questions/does-the-store-session-match-the-other-two.md +19 -0
- package/knowledge/questions/index.md +25 -2
- package/knowledge/questions/is-the-agent-score-worker-reachable-without-a-session.md +21 -0
- package/knowledge/questions/live-scheduled-tests-may-write.md +19 -0
- package/knowledge/questions/provision-virtual-users-has-no-caller-in-this-repo.md +23 -0
- package/knowledge/questions/remote-secret-refuses-requests-without-a-remote-token-on-every-path.md +32 -0
- package/knowledge/questions/should-a-middleware-set-session-persist-to-the-store.md +25 -0
- package/knowledge/questions/should-a-scoped-secret-view-throw-from-has-secret-for-a-disallowed-key.md +23 -0
- package/knowledge/questions/should-a-wrong-bearer-token-be-anonymous-or-refused.md +35 -0
- package/knowledge/questions/should-an-empty-webhook-signing-secret-send-unsigned.md +23 -0
- package/knowledge/questions/should-http-virtual-user-sessions-reset-between-scenarios-in-one-run.md +27 -0
- package/knowledge/questions/should-the-cookie-jar-scope-cookies-to-the-api-origin.md +26 -0
- package/knowledge/questions/should-voice-input-fetch-audio-urls-without-a-host-allowlist.md +20 -0
- package/knowledge/questions/staging-impersonation-rules.md +27 -0
- package/knowledge/questions/virtual-user-keys-on-cloud-stages-are-pikku-studios-to-build.md +34 -0
- package/knowledge/questions/virtual-user-statement-replay-is-per-process.md +20 -0
- package/knowledge/questions/what-an-impersonated-session-carries.md +22 -0
- package/package.json +2 -3
- package/src/public-surface.json +38 -47
- package/dist/services/persona-actor-secret.d.ts +0 -38
- package/dist/services/persona-actor-secret.js +0 -39
- package/dist/services/persona-sign-in.d.ts +0 -117
- package/dist/services/persona-sign-in.js +0 -154
- package/dist/wirings/actor-flow/index.d.ts +0 -2
- package/dist/wirings/actor-flow/run-conversation.d.ts +0 -36
- package/dist/wirings/persona/define-personas.d.ts +0 -32
- package/dist/wirings/persona/index.d.ts +0 -23
- package/dist/wirings/persona/index.js +0 -19
- package/knowledge/decisions/internals/actor-flow-conversations-seed-a-hidden-kickoff-message.md +0 -23
- package/knowledge/decisions/internals/actor-flow-verdicts-are-llm-self-evaluations.md +0 -25
- package/knowledge/decisions/internals/validate-checks-personas-through-a-shared-module.md +0 -43
- package/knowledge/decisions/security/actor-sign-in-only-works-for-actor-flagged-users.md +0 -31
- package/knowledge/decisions/security/an-actor-credential-is-derived-per-persona.md +0 -42
- package/knowledge/decisions/security/pikku-carries-actor-scopes-as-data-and-the-app-grants-them.md +0 -26
- /package/dist/wirings/{actor-flow/actor-flow.types.js → virtual-user/conversation/conversation.types.js} +0 -0
- /package/dist/wirings/{actor-flow → virtual-user/conversation}/index.js +0 -0
- /package/dist/wirings/{persona/persona.types.js → virtual-user/virtual-user-declaration.types.js} +0 -0
- /package/dist/wirings/{persona/persona-mailbox.js → virtual-user/virtual-user-mailbox.js} +0 -0
package/knowledge/decisions/internals/validate-checks-virtual-users-through-a-shared-module.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: Both validators check virtual users through one shared module, and a virtual user counts from either the meta or the source
|
|
4
|
+
description: workspace validate and cloud validate are separate implementations, so the virtual user/virtual-user/knowledge checks live in virtual-user-checks.ts and are called by both — and a virtual user is counted from the generated meta or a defineVirtualUsers() source scan, whichever answers first
|
|
5
|
+
tags: cli, validate, scenarios, virtual users
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Both validators check virtual users through one shared module, and a virtual user counts from either the meta or the source
|
|
9
|
+
|
|
10
|
+
`pikku workspace validate` and `pikku validate` read like one command
|
|
11
|
+
with a flag. They are not. They are two implementations that walk the same
|
|
12
|
+
project and duplicate about eighteen findings between them verbatim —
|
|
13
|
+
`functions-dir-missing` exists twice, character for character, in two files.
|
|
14
|
+
Pikku Studio Cloud adds the deploy-shaped checks (themes, frontends, the Cloudflare
|
|
15
|
+
adapter, the `.gitignore` contract); workspace adds the local-development ones
|
|
16
|
+
(`dev.db`, the auth migrations).
|
|
17
|
+
|
|
18
|
+
So a check written into either one is a check half the projects never run. An
|
|
19
|
+
app that never deploys through cloud only ever sees workspace validate; an app
|
|
20
|
+
that does sees cloud validate in CI. The virtual user checks are the kind that only
|
|
21
|
+
pay off when nobody remembers to ask for them, which means they had to be in
|
|
22
|
+
both. Rather than duplicate them a nineteenth time, they live in
|
|
23
|
+
`virtual-user-checks.ts` and both validators call `runVirtualUserChecks`. Merging the
|
|
24
|
+
two validators outright is the better fix and a much larger one; this does not
|
|
25
|
+
block it.
|
|
26
|
+
|
|
27
|
+
**A virtual user counts from either source, and either alone is wrong at a
|
|
28
|
+
predictable moment.** The generated `pikku-virtual-users-meta.gen.json` is empty on
|
|
29
|
+
a fresh clone where codegen has not run, so trusting only it means a warning
|
|
30
|
+
that fires on every new checkout — and a validator that cries wolf gets muted,
|
|
31
|
+
after which it never says anything worth hearing. A source scan for
|
|
32
|
+
`defineVirtualUsers(` cannot see virtual users an addon contributed. Declared by either
|
|
33
|
+
counts as declared.
|
|
34
|
+
|
|
35
|
+
**Everything here is `warn`, never `error`.** A project with no virtual users is
|
|
36
|
+
under-tested, not broken, and `validate` gating a deploy on it would make the
|
|
37
|
+
first thing anyone does with the check be to remove it. `knowledge-empty` stays
|
|
38
|
+
at the `info` the shared `@pikku/knowledge` package already assigned it rather
|
|
39
|
+
than being raised to match — the severity belongs to that package.
|
|
40
|
+
|
|
41
|
+
**What this rules out:** duplicating the checks into both validators;
|
|
42
|
+
adding them to only one; deriving the virtual user count from the generated meta
|
|
43
|
+
alone; failing a build over a missing virtual user.
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
type: decision
|
|
3
|
-
title: The
|
|
3
|
+
title: The conversation engine only sees a transport-agnostic target driver
|
|
4
4
|
description: The engine never imports the agent runner; the target is injected as run/approve, so scenarios exercise the real wire path
|
|
5
|
-
tags:
|
|
5
|
+
tags: virtual-user-conversation
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# The
|
|
8
|
+
# The conversation engine only sees a transport-agnostic target driver
|
|
9
9
|
|
|
10
10
|
`runConversation` reaches the target agent exclusively through the injected
|
|
11
11
|
`TargetAgentDriver` — `run(message)` and `approve(runId, decisions)` — declared
|
|
12
|
-
in `packages/core/src/wirings/
|
|
13
|
-
driver is HTTP-backed, issuing the
|
|
14
|
-
the signed-in
|
|
12
|
+
in `packages/core/src/wirings/virtual-user/conversation/conversation.types.ts`. In production that
|
|
13
|
+
driver is HTTP-backed, issuing the virtual user's `agentRun` and `agentApprove` calls as
|
|
14
|
+
the signed-in virtual user.
|
|
15
15
|
|
|
16
16
|
Keeping the seam transport-agnostic is what makes the conversation a real test:
|
|
17
17
|
the agent is exercised through the same authenticated wire path a user takes,
|
package/knowledge/decisions/internals/virtual-user-conversation-verdicts-are-llm-self-evaluations.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A conversation verdict is the virtual user's self-evaluation, not an assertion
|
|
4
|
+
description: The engine returns what the virtual user judged plus the transcript; deterministic checks stay with the caller
|
|
5
|
+
tags: virtual-user-conversation
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A conversation verdict is the virtual user's self-evaluation, not an assertion
|
|
9
|
+
|
|
10
|
+
`runConversation` ends by asking the virtual user LLM whether the `evaluate`
|
|
11
|
+
criterion was met and returns that as `ConversationVerdict` — `passed`, `reasoning`
|
|
12
|
+
and the full `transcript`
|
|
13
|
+
(`packages/core/src/wirings/virtual-user/conversation/`). Nothing in the engine inspects the
|
|
14
|
+
system the agent acted on.
|
|
15
|
+
|
|
16
|
+
The verdict is deliberately soft because the thing it judges is soft: whether a
|
|
17
|
+
conversation accomplished a natural-language task. Deterministic checks — that a
|
|
18
|
+
row was written, that an email went out — belong to the caller, which already
|
|
19
|
+
holds the virtual user and can `virtualUser.invoke(...)` after the conversation returns. The
|
|
20
|
+
`reasoning` and `transcript` fields exist so a soft failure is debuggable rather
|
|
21
|
+
than merely red.
|
|
22
|
+
|
|
23
|
+
**What this rules out:** having `runConversation` assert or throw on a failed
|
|
24
|
+
verdict, and adding deterministic post-conditions to `ConverseOptions` instead of
|
|
25
|
+
running them at the call site.
|
package/knowledge/decisions/internals/virtual-user-conversations-seed-a-hidden-kickoff-message.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A virtual user conversation starts from a seeded kickoff message
|
|
4
|
+
description: The virtual user's first turn needs a non-empty message list because providers reject an empty prompt; the seed is an instruction and stays out of the transcript
|
|
5
|
+
tags: virtual-user-conversation
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A virtual user conversation starts from a seeded kickoff message
|
|
9
|
+
|
|
10
|
+
`runConversation` in
|
|
11
|
+
`packages/core/src/wirings/virtual-user/conversation/run-conversation.ts` primes
|
|
12
|
+
`virtualUserMessages` with a single user message telling the virtual user to begin, before
|
|
13
|
+
the first turn is generated.
|
|
14
|
+
|
|
15
|
+
The virtual user speaks first, so at that point there is nothing for it to reply to —
|
|
16
|
+
and LLM providers reject a completion request with no messages. The seed is
|
|
17
|
+
addressed to the virtual user rather than to the target agent, which is why it is never
|
|
18
|
+
pushed onto `transcript`: it is scaffolding, not something the simulated user
|
|
19
|
+
said, and including it would corrupt the transcript the final evaluation reads.
|
|
20
|
+
|
|
21
|
+
**What this rules out:** starting from an empty `virtualUserMessages` array, folding
|
|
22
|
+
the kickoff into the virtual user instructions where a provider may weight it
|
|
23
|
+
differently, and pushing the seed into the transcript for symmetry.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: Virtual users are declared in exactly one defineVirtualUsers call per codebase
|
|
4
|
+
description: defineVirtualUsers is a no-op declaration that the inspector reads statically, and a second call anywhere in the codebase fails the build
|
|
5
|
+
tags: core, inspector, virtual-users, declaration
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Virtual users are declared in exactly one defineVirtualUsers call per codebase
|
|
9
|
+
|
|
10
|
+
`defineVirtualUsers` in `packages/core/src/wirings/virtual-user/define-virtual-users.ts`
|
|
11
|
+
is a no-op (`define-virtual-users.ts:33`). The CLI reads the call by AST and
|
|
12
|
+
generates a `VirtualUserId` union and `virtual-users.gen.json` from it
|
|
13
|
+
(`define-virtual-users.ts:3-8`). The doc comment states the rule: "Exactly one
|
|
14
|
+
`defineVirtualUsers(...)` is allowed per codebase, so there is one place to read the
|
|
15
|
+
cast from and one place to add to. A second call — even in the same file — fails
|
|
16
|
+
the build" (`define-virtual-users.ts:14-16`).
|
|
17
|
+
|
|
18
|
+
The inspector enforces it. `addVirtualUsers` requires an inline object literal and
|
|
19
|
+
refuses anything else (`packages/inspector/src/add/add-virtual-users.ts:163-170`),
|
|
20
|
+
then claims the single declaration (`add-virtual-users.ts:174-181`, error
|
|
21
|
+
`DUPLICATE_VIRTUAL_USERS_DEFINITION`). `claimSingleDeclaration` in
|
|
22
|
+
`packages/inspector/src/utils/single-declaration.ts` refuses a second call in the
|
|
23
|
+
same file or another one (`single-declaration.ts:44-56`). Generated `.gen.ts` files
|
|
24
|
+
are exempt and never claim the slot (`single-declaration.ts:40-42`).
|
|
25
|
+
|
|
26
|
+
**What this rules out:** a second `defineVirtualUsers` call, including in the same
|
|
27
|
+
file, and splitting the cast across several declarations. The rule is about where a
|
|
28
|
+
person adds a virtual user, so it applies to hand-written files only.
|
|
29
|
+
|
|
30
|
+
**Related:** [scenario and virtual-user files are named for what they hold](scenarios-live-in-files-named-for-them.md)
|
|
@@ -31,12 +31,12 @@ matches a family of ids, `containing` picks the match holding a piece of text,
|
|
|
31
31
|
and `within` scopes the lookup to one row or section. Core defines the shape;
|
|
32
32
|
the driver resolves it against a real page.
|
|
33
33
|
|
|
34
|
-
The same dependency discipline applies at runtime: `
|
|
35
|
-
`pikku-scenario-service.ts` imports the HTTP
|
|
36
|
-
runner bundle only pays for the AI
|
|
37
|
-
actually signs
|
|
34
|
+
The same dependency discipline applies at runtime: `resolveScenarioVirtualUsers` in
|
|
35
|
+
`pikku-scenario-service.ts` imports the HTTP virtual user client lazily, so even a
|
|
36
|
+
runner bundle only pays for the AI virtual user conversation loop when a scenario
|
|
37
|
+
actually signs a virtual user in.
|
|
38
38
|
|
|
39
39
|
**What this rules out:** importing playwright (or any driver) from core,
|
|
40
40
|
tightening `reset`/`captureFailure` to required, letting `captureFailure` throw,
|
|
41
|
-
reducing `TestIdSelector` to a plain string, or making the
|
|
41
|
+
reducing `TestIdSelector` to a plain string, or making the virtual-user-client import
|
|
42
42
|
static.
|
package/knowledge/decisions/internals/workflow-run-capabilities-are-extensions-not-subclasses.md
CHANGED
|
@@ -18,7 +18,7 @@ The reason it is not a subclass is bundle size: a bundler drops an unused
|
|
|
18
18
|
_module_ but never an unused class member. Anything declared on
|
|
19
19
|
`PikkuWorkflowService` ships in every server built on Pikku, along with
|
|
20
20
|
everything it imports. `PikkuScenarioService` (`pikku-scenario-service.ts`) is
|
|
21
|
-
the one implementation today — steps,
|
|
21
|
+
the one implementation today — steps, virtual users, lifecycle hooks, the browser
|
|
22
22
|
provider and the assertion wire members — and scenarios only ever run from
|
|
23
23
|
`pikku scenario run`, so the whole surface stays behind an import that only the
|
|
24
24
|
runner makes. It is not a workflow service in its own right because a scenario
|
|
@@ -33,7 +33,7 @@ installed rather than inherited. It pairs the extension with
|
|
|
33
33
|
single external process driving a deployed app over its real transport: there
|
|
34
34
|
is nothing to persist and no second worker to resume it.
|
|
35
35
|
|
|
36
|
-
**What this rules out:** moving scenario steps,
|
|
36
|
+
**What this rules out:** moving scenario steps, virtual users, hooks or the browser
|
|
37
37
|
provider onto `PikkuWorkflowService`, making `PikkuScenarioService` extend it,
|
|
38
38
|
or widening `WorkflowRunEngine` into "just pass the service" — each one puts the
|
|
39
39
|
scenario runtime, and its transitive imports, into every deployed Pikku server.
|
|
@@ -33,10 +33,10 @@ defaults are shorter than the assertion wrappers': `within` 15s, `interval`
|
|
|
33
33
|
`requireScenarioEnv` (`scenario-step-guards.ts`) exists for the mirror-image
|
|
34
34
|
reason: `env` is optional on the step wire because most steps need nothing from
|
|
35
35
|
it, so the narrowing happens in one place with a message that says what to do,
|
|
36
|
-
instead of every step file writing its own guard. The
|
|
37
|
-
all — a step that runs as somebody declares `
|
|
36
|
+
instead of every step file writing its own guard. The virtual user is not narrowed at
|
|
37
|
+
all — a step that runs as somebody declares `virtualUser: true` (or a `browser`
|
|
38
38
|
binding, which cannot be provisioned without a window to open), and the runner
|
|
39
|
-
injects `wire.
|
|
39
|
+
injects `wire.virtualUser` non-optionally into every binding and refuses to dispatch
|
|
40
40
|
the step without one. A wire member can be required per binding; a property of
|
|
41
41
|
one cannot, which is why it is not on `scenarioStep`. `scenarioStep`'s `verifyStepName` call doubles as the guard for `then`
|
|
42
42
|
being a wire member — an accidental `await scenario` calls it with a resolve
|
|
@@ -50,5 +50,5 @@ the first argument only; `times` means at-least-once unless a count is given;
|
|
|
50
50
|
|
|
51
51
|
**What this rules out:** giving scenario steps the workflow retry default,
|
|
52
52
|
recording each poll iteration as its own step, treating a falsy `pollUntil`
|
|
53
|
-
result as "not yet", making `pollUntil` throw on timeout, or narrowing `
|
|
53
|
+
result as "not yet", making `pollUntil` throw on timeout, or narrowing `virtualUser`
|
|
54
54
|
by hand inside individual step files.
|
package/knowledge/decisions/internals/workflow-scenario-hooks-are-a-scenario-only-affordance.md
CHANGED
|
@@ -16,7 +16,7 @@ pass by a single external process.
|
|
|
16
16
|
A hook is not a pikku function: it has no id, no meta and no schema, so it
|
|
17
17
|
cannot go through `runPikkuFunc` and the runner records nothing for it. It gets
|
|
18
18
|
exactly what the scenario body gets — the same wire (which is how it reaches the
|
|
19
|
-
app through `
|
|
19
|
+
app through `virtualUsers`) and singleton services composed with this invocation's
|
|
20
20
|
wire services — and nothing else. `ScenarioHookError` keeps the original error
|
|
21
21
|
as `cause`, so the failure that actually happened is never lost behind the label
|
|
22
22
|
saying which phase it happened in.
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
type: decision
|
|
3
3
|
title: Scenario prose is rendered out of typed calls, not parsed into them
|
|
4
|
-
description:
|
|
4
|
+
description: A readable report with no regex step registry to maintain
|
|
5
5
|
tags: workflow
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Scenario prose is rendered out of typed calls, not parsed into them
|
|
9
9
|
|
|
10
10
|
`scenario-prose.ts` renders the English sentence a reporter shows for a scenario
|
|
11
|
-
step.
|
|
11
|
+
step. Instead of parsing English into a call
|
|
12
12
|
through a registry of regexes, the call is typed and the English is rendered out
|
|
13
13
|
of it. The readable report survives without anyone maintaining a step-definition
|
|
14
14
|
registry, and a step that no longer exists cannot leave a dangling phrase behind.
|
|
@@ -19,7 +19,7 @@ generated `TypedScenario` narrows these over `FlattenedScenarioStepMap`.
|
|
|
19
19
|
prose a reporter renders (`scenario-prose.ts`), never what executes.
|
|
20
20
|
|
|
21
21
|
The prose direction is itself the decision: rather than parsing English into a
|
|
22
|
-
call
|
|
22
|
+
call through a regex registry, `composeStepProse` renders English out of a typed
|
|
23
23
|
call, so a readable report survives without a regex registry paying for it. It
|
|
24
24
|
lives in core so the CLI reporter and the console render the same sentence for
|
|
25
25
|
the same step, and `renderStepTemplate` fills `{placeholders}` from the input
|
package/knowledge/decisions/internals/workflow-suspended-runs-keep-their-in-process-context.md
CHANGED
|
@@ -11,7 +11,7 @@ tags: workflow
|
|
|
11
11
|
`completed | failed | cancelled`. `suspended` is deliberately absent: a
|
|
12
12
|
suspended run stops a poll loop but can still be resumed, so anything the
|
|
13
13
|
process holds for it — the run context, an extension's per-run state such as a
|
|
14
|
-
scenario's live
|
|
14
|
+
scenario's live virtual user clients — has to survive. `WORKFLOW_END_STATES`, which
|
|
15
15
|
`awaitRunEnd` uses to stop reading, does include `suspended`, because a poller
|
|
16
16
|
should not wait on a run that needs an external nudge. The two sets are not
|
|
17
17
|
interchangeable.
|
package/knowledge/decisions/security/a-bearer-token-checked-against-a-secret-reads-it-per-request.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A bearer token checked against a secret is read per request, and an unreadable secret means no session
|
|
4
|
+
description: The secretId form of authBearer reads the secret on every request; a missing or failing read leaves the request without a session rather than raising an error
|
|
5
|
+
tags: core, middleware, auth
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A bearer token checked against a secret is read per request, and an unreadable secret means no session
|
|
9
|
+
|
|
10
|
+
In `packages/core/src/middleware/auth-bearer.ts:57-70`, a token given as a
|
|
11
|
+
literal `value` is compared directly (`:59-60`). A token given a `secretId` is
|
|
12
|
+
read from the secret service on each request (`:63-64`). A failed read is caught
|
|
13
|
+
and becomes `undefined` (`:65`), and the code comments that an unset secret means
|
|
14
|
+
the feature is off, never a request error (`:62`).
|
|
15
|
+
|
|
16
|
+
An undefined expected value skips the comparison (`:68`). `userSession` stays
|
|
17
|
+
null, and `setSession` is not called (`:55`, `:75-77`). A malformed scheme is a
|
|
18
|
+
different case: it throws `InvalidSessionError` (`:50-53`).
|
|
19
|
+
|
|
20
|
+
The static and JWT branches of this middleware are covered by the existing
|
|
21
|
+
questions `a-bearer-jwt-with-no-identity-becomes-a-session.md` and
|
|
22
|
+
`should-a-wrong-bearer-token-be-anonymous-or-refused.md`.
|
|
23
|
+
|
|
24
|
+
**What this rules out:** turning a missing or unreadable signing secret into a
|
|
25
|
+
server error, and caching the secret value across requests in the middleware
|
|
26
|
+
closure.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A call without a session raises MissingSessionError, and a missing scope raises MissingScopeError
|
|
4
|
+
description: The function runner separates the no-session refusal from the scope and permission refusals, so a caller can tell a missing login from a denied grant
|
|
5
|
+
tags: core, errors, sessions, scopes
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A call without a session raises MissingSessionError, and a missing scope raises MissingScopeError
|
|
9
|
+
|
|
10
|
+
`resolveSession` in `packages/core/src/function/function-runner.ts` (line 64) loads a stored session when there is a user id. It throws nothing.
|
|
11
|
+
|
|
12
|
+
The refusal is in `executeFunction`. A sessionless function requires a session only when its wiring, its own config, or the addon says `auth: true` (`function-runner.ts:385-395`). That refusal throws `MissingSessionError` (`function-runner.ts:392`). A `pikkuFunc` with no session throws the same error unconditionally (`function-runner.ts:402`). The class is at `packages/core/src/errors/errors.ts:36`.
|
|
13
|
+
|
|
14
|
+
Scope checks use a different error. `verifyScopes` throws `MissingScopeError` (`packages/core/src/scopes.ts:84-86`, class at `packages/core/src/errors/errors.ts:118`).
|
|
15
|
+
|
|
16
|
+
Permission checks use `ForbiddenError` with the message `Permission denied` (`packages/core/src/permissions.ts:155`, `:166`, class at `packages/core/src/errors/errors.ts:57`).
|
|
17
|
+
|
|
18
|
+
So the three refusals are distinct: no login, a missing scope, and a failed permission function.
|
|
19
|
+
|
|
20
|
+
**What this rules out:** reporting a call with no session as a `ForbiddenError`, and reading a `ForbiddenError` from the runner as proof that a session exists but lacks a grant.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A cookie the caller sets wins over the jar's copy of the same name
|
|
4
|
+
description: createCookieJar sends the caller's cookie for a name and drops the jar's copy of that name, so one request never carries the same cookie twice
|
|
5
|
+
tags: core, scenario, cookies
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A cookie the caller sets wins over the jar's copy of the same name
|
|
9
|
+
|
|
10
|
+
In `createCookieJar` (`packages/core/src/services/scenario-cookie-jar.ts`)
|
|
11
|
+
the jar builds the outgoing `cookie` header from the caller's own header and the
|
|
12
|
+
jar's held cookies. A held cookie is left out when the caller's header already
|
|
13
|
+
names it. The code comment says a request that already carries a session "is
|
|
14
|
+
stating which one it means, and emitting the jar's copy alongside it sends the
|
|
15
|
+
same name twice."
|
|
16
|
+
|
|
17
|
+
The rule is per name, not per request. Cookies the caller did not name still go
|
|
18
|
+
out from the jar. The jar's `Set-Cookie` handling, which replaces a reissued
|
|
19
|
+
cookie rather than sending both, is covered by
|
|
20
|
+
[Scenario sessions are isolated per virtual user and reset between scenarios](workflow-scenario-sessions-are-isolated-per-virtual-user-and-per-scenario.md).
|
|
21
|
+
|
|
22
|
+
**What this rules out:** sending two values of one cookie name in a request, and
|
|
23
|
+
letting the jar's held session override a session the caller has chosen to send.
|
package/knowledge/decisions/security/a-channel-lifecycle-acts-as-the-socket-upgrade-session.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A channel lifecycle acts as the session the socket upgrade established
|
|
4
|
+
description: onConnect and onDisconnect run with the userSession captured during the socket upgrade, both in the wire they receive and as the session service passed to runPikkuFunc
|
|
5
|
+
tags: core, channel, sessions, lifecycle
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A channel lifecycle acts as the session the socket upgrade established
|
|
9
|
+
|
|
10
|
+
`runChannelLifecycleWithMiddleware` in `packages/core/src/wirings/channel/channel-common.ts` takes an optional `userSession`. The comment says it is established while the socket upgrades, so `onConnect` can tell who connected (`channel-common.ts:43-47`). The same function also serves `connect` and `disconnect` (`channel-common.ts:38`).
|
|
11
|
+
|
|
12
|
+
The wire for the lifecycle is built from that session when one exists (`channel-common.ts:78`). The lifecycle function then runs through `runPikkuFunc` with `sessionService: userSession` (`channel-common.ts:84-93`). So the lifecycle reads and writes the same session the upgrade produced, for both connect and disconnect.
|
|
13
|
+
|
|
14
|
+
The related decision that records when the user id is stored is [a channel's user id is persisted only after onConnect middleware has run](../internals/channel-user-id-is-persisted-after-onconnect-middleware-runs.md).
|
|
15
|
+
|
|
16
|
+
**What this rules out:** running a channel's disconnect handler as a different identity than the one that connected the socket, and building the lifecycle wire without the upgrade session when one was established.
|
package/knowledge/decisions/security/a-channel-requires-a-session-unless-it-says-auth-false.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A channel requires a session unless it says auth: false
|
|
4
|
+
description: The channel-level requirement is auth !== false, and a message handler's own auth, when it is set, decides for that message in either direction
|
|
5
|
+
tags: core, channel, auth
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A channel requires a session unless it says auth: false
|
|
9
|
+
|
|
10
|
+
Both channel runners compute the channel-level requirement as
|
|
11
|
+
`channelConfig.auth !== false`: `packages/core/src/wirings/channel/channel-runner.ts:142`
|
|
12
|
+
and `packages/core/src/wirings/channel/channel-handler.ts:75`. An absent `auth`
|
|
13
|
+
therefore requires a session. `channel.types.ts:67-76` documents the same rule and
|
|
14
|
+
says why: a proxy in front of a channel needs to tell public from private.
|
|
15
|
+
|
|
16
|
+
Per message, `validateAuth` (`channel-handler.ts:49-65`) uses the channel's
|
|
17
|
+
`requiresSession` when the handler is a bare function or has no `auth` of its own
|
|
18
|
+
(`:50-58`). When the handler object sets `auth`, that value wins (`:58-60`). The
|
|
19
|
+
check then fails only when `auth` is true and there is no session (`:62-64`).
|
|
20
|
+
|
|
21
|
+
So a message handler's `auth: false` loosens a channel that requires a session for
|
|
22
|
+
that message, and `auth: true` tightens a public channel for that message. A
|
|
23
|
+
rejected message gets the reply described in the question
|
|
24
|
+
`unauthorized-channel-replies-escape-the-declared-out-type.md`.
|
|
25
|
+
|
|
26
|
+
**What this rules out:** treating a channel with no `auth` flag as public.
|
package/knowledge/decisions/security/a-credential-read-path-follows-its-declared-type-not-absence.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A credential's read path follows its declared type, never a missing value
|
|
4
|
+
description: The credential wire service reads a wire credential only from the user's store and a singleton only from the deployment, so an absent user value never falls back to the deployment's token
|
|
5
|
+
tags: core, credentials, wire, singleton
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A credential's read path follows its declared type, never a missing value
|
|
9
|
+
|
|
10
|
+
`packages/core/src/services/credential-wire-service.ts` decides where each
|
|
11
|
+
credential is read from. The doc comment says the choice is "decided by what the
|
|
12
|
+
credential IS — its declared type, as the wiring may have overridden it — and
|
|
13
|
+
never by whether a per-user lookup came back empty", because an absence-driven
|
|
14
|
+
fallback "would let a user who has not connected read the deployment's own token"
|
|
15
|
+
(`credential-wire-service.ts:6-10`).
|
|
16
|
+
|
|
17
|
+
The resolution type is `{ mode: 'wire' } | { mode: 'singleton' }`
|
|
18
|
+
(`credential-wire-service.ts:12`).
|
|
19
|
+
|
|
20
|
+
In `doLoad`, the user's store is read with `getAll(userId)`, and any name the
|
|
21
|
+
resolution marks `singleton` is skipped (`credential-wire-service.ts:103-104`).
|
|
22
|
+
The comment says this stops a user "in charge of a slot the deployment owns"
|
|
23
|
+
(`credential-wire-service.ts:116-121`).
|
|
24
|
+
|
|
25
|
+
`loadDeploymentCredentials` fetches only `singleton` names from the deployment
|
|
26
|
+
service (`credential-wire-service.ts:137-143`). A `wire` name is skipped there
|
|
27
|
+
(`credential-wire-service.ts:138`), and the comment says a wire credential "can
|
|
28
|
+
never pick up a platform value" (`credential-wire-service.ts:127-130`).
|
|
29
|
+
|
|
30
|
+
**What this rules out:** a `wire` credential falling back to the deployment's
|
|
31
|
+
value when the user has none, and a user's stored value being imported under a
|
|
32
|
+
name the deployment declared as `singleton`.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A credential's wire or singleton type is a literal the inspector checks at declaration
|
|
4
|
+
description: defineCredential is a no-op read by AST, so its type must be a literal 'wire' or 'singleton' and the inspector refuses any other value
|
|
5
|
+
tags: core, credentials, inspector, declaration
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A credential's wire or singleton type is a literal the inspector checks at declaration
|
|
9
|
+
|
|
10
|
+
`defineCredential` in `packages/core/src/wirings/credential/define-credential.ts` is a
|
|
11
|
+
no-op. Its doc comment says the CLI reads the call by AST, so "the declaration must
|
|
12
|
+
be a top-level literal" (`define-credential.ts:4-5`). `type: 'wire'` is per-user and
|
|
13
|
+
`type: 'singleton'` is platform-wide (`define-credential.ts:6`).
|
|
14
|
+
|
|
15
|
+
The inspector finds the call by its identifier (`packages/inspector/src/add/add-credential.ts:27`)
|
|
16
|
+
and requires a `type` property. Anything other than `'singleton'` or `'wire'` is a
|
|
17
|
+
critical error: "missing or has invalid 'type' property. Must be 'singleton' or
|
|
18
|
+
'wire'" (`add-credential.ts:64-68`).
|
|
19
|
+
|
|
20
|
+
The read path that follows from that type is in
|
|
21
|
+
[a credential's read path follows its declared type, never a missing value](a-credential-read-path-follows-its-declared-type-not-absence.md).
|
|
22
|
+
This note covers the declaration only: the type is decided where the credential is
|
|
23
|
+
declared, and the inspector rejects a declaration whose type is missing or not one of
|
|
24
|
+
the two literals.
|
|
25
|
+
|
|
26
|
+
**What this rules out:** computing a credential's type from a variable or at runtime,
|
|
27
|
+
and accepting a declaration whose type is missing or spelled differently.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A first-party tracking cookie is written only after its purposes are consented
|
|
4
|
+
description: mintCookie writes a new cookie only when every purpose it requires is true in the consent record, and writes nothing on a wire with no HTTP response
|
|
5
|
+
tags: core, analytics, consent, cookies
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A first-party tracking cookie is written only after its purposes are consented
|
|
9
|
+
|
|
10
|
+
`packages/core/src/analytics/mint-cookie.ts` creates a first-party cookie when
|
|
11
|
+
none exists. The doc comment says writing the cookie "IS the act consent governs",
|
|
12
|
+
so a minter that runs before the banner is answered has already done what the
|
|
13
|
+
send gate was meant to prevent (`mint-cookie.ts:11-15`).
|
|
14
|
+
|
|
15
|
+
The consent check is `permitted`. It returns true when no purposes are required,
|
|
16
|
+
and otherwise requires each purpose to be exactly `true` in the consent record
|
|
17
|
+
(`mint-cookie.ts:29-35`). A purpose missing from the record counts as refused.
|
|
18
|
+
`mintCookie` runs that check after it has found no existing cookie and no cached
|
|
19
|
+
value (`mint-cookie.ts:72`).
|
|
20
|
+
|
|
21
|
+
An existing cookie is returned before the consent check (`mint-cookie.ts:63-64`).
|
|
22
|
+
Reading a cookie the visitor already holds is therefore not gated by consent. The
|
|
23
|
+
`overwrite` option is the only way to replace it (`mint-cookie.ts:19-26`).
|
|
24
|
+
|
|
25
|
+
A wire with no HTTP response returns `undefined` and writes nothing
|
|
26
|
+
(`mint-cookie.ts:70-71`), as the doc comment says for cron tasks and queue workers
|
|
27
|
+
(`mint-cookie.ts:50-53`).
|
|
28
|
+
|
|
29
|
+
The value is cached per wire, so a second call in one request returns the same
|
|
30
|
+
identity (`mint-cookie.ts:66-68`, `77`, and the comment at `mint-cookie.ts:46-48`).
|
|
31
|
+
|
|
32
|
+
**What this rules out:** writing a tracking cookie before its purposes are
|
|
33
|
+
consented, and minting an identity on a non-HTTP wire.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A readonly session reaches only functions declared readonly
|
|
4
|
+
description: A session flagged readonly is refused by every function not declared readonly, checked straight after the session is required and before scopes
|
|
5
|
+
tags: core, auth, function-runner
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A readonly session reaches only functions declared readonly
|
|
9
|
+
|
|
10
|
+
`CoreUserSession.readonly` (`packages/core/src/types/core.types.ts`) restricts a
|
|
11
|
+
session to functions declared `readonly`. In `runPikkuFunc`
|
|
12
|
+
(`packages/core/src/function/function-runner.ts`) the check is
|
|
13
|
+
`if (session?.readonly && !funcMeta.readonly) throw new ReadonlySessionError()`.
|
|
14
|
+
It runs after the missing-session check and before scopes, permissions and input
|
|
15
|
+
validation, so a readonly session is refused before any work is done for a
|
|
16
|
+
mutating function. The unit test "should reject readonly sessions for
|
|
17
|
+
non-readonly functions" in `function-runner.test.ts` pins the behaviour.
|
|
18
|
+
|
|
19
|
+
Core never sets the flag. Whoever builds the session decides it, and core only
|
|
20
|
+
enforces it. Readonly is a property of the session, not of a scope, so a scope
|
|
21
|
+
grant cannot widen what a readonly session may reach.
|
|
22
|
+
|
|
23
|
+
**What this rules out:** expressing read-only access as a scope the session
|
|
24
|
+
happens to lack, and letting a readonly session call a mutating function on the
|
|
25
|
+
grounds that its scopes cover it.
|
package/knowledge/decisions/security/a-scaffold-flag-says-a-surface-exists-not-who-may-call-it.md
CHANGED
|
@@ -13,11 +13,16 @@ The field is gone. `PikkuScaffoldFeature` is now `boolean | { path?: string }`:
|
|
|
13
13
|
whether the surface is generated, and where the file goes.
|
|
14
14
|
|
|
15
15
|
The field was a second gate in front of one that already runs. `resolveSession`
|
|
16
|
-
in `packages/core/src/function/function-runner.ts`
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
`
|
|
20
|
-
|
|
16
|
+
in `packages/core/src/function/function-runner.ts` (line 64) only loads a stored
|
|
17
|
+
session; it throws nothing. The refusal is in `executeFunction`: a `pikkuFunc`
|
|
18
|
+
with no session throws `MissingSessionError` (line 402, class at
|
|
19
|
+
`packages/core/src/errors/errors.ts:36`), unconditionally. A
|
|
20
|
+
`pikkuSessionlessFunc` throws the same error (line 392) only when its own
|
|
21
|
+
`auth: true` says so, when its wiring says so, or when `resolveAddonAuth` does. That is where authentication is decided, per function,
|
|
22
|
+
by whoever wrote it. Neither refusal is a `ForbiddenError`: both throw `MissingSessionError`
|
|
23
|
+
(`function-runner.ts:392`, `:402`; class at `packages/core/src/errors/errors.ts:36`). The split between
|
|
24
|
+
that error, `MissingScopeError` and `ForbiddenError` is recorded in
|
|
25
|
+
[a call without a session raises MissingSessionError](./a-call-without-a-session-raises-missing-session-not-forbidden.md). A config flag three directories away could only be coarser
|
|
21
26
|
than that, and being coarser was the whole problem the earlier decision was
|
|
22
27
|
trying to work around by defaulting it closed.
|
|
23
28
|
|
package/knowledge/decisions/security/a-scheduled-virtual-user-run-needs-no-admin-at-run-time.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A scheduled virtual-user run needs no admin at run time
|
|
4
|
+
description: An admin may start a virtual-user run, but the run itself never depends on an admin being signed in or on an admin's credential
|
|
5
|
+
tags: services, auth, workflow
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A scheduled virtual-user run needs no admin at run time
|
|
9
|
+
|
|
10
|
+
The people who set up virtual-user runs are not technical. A run must keep
|
|
11
|
+
working after they stop being around, and it must not depend on any admin being
|
|
12
|
+
signed in or on any admin's credential.
|
|
13
|
+
|
|
14
|
+
An admin clicking "run" or creating a schedule is the authorization step. Once
|
|
15
|
+
the schedule exists, the run is started by the app's own scheduler and performs
|
|
16
|
+
the test as a provisioned test account (see
|
|
17
|
+
[virtual-user-flagged users only](virtual-user-sign-in-only-works-for-virtual-user-flagged-users.md)).
|
|
18
|
+
|
|
19
|
+
The run presents no credential at all. `executeVirtualUserRun`
|
|
20
|
+
(`packages/core/src/wirings/virtual-user/virtual-user-scaffold.ts`) builds its
|
|
21
|
+
virtual users with `signIn: inProcessSignIn(auth)`, which calls the app's
|
|
22
|
+
server-only sign-in endpoint through `auth.api`
|
|
23
|
+
(`packages/core/src/services/virtual-user-sign-in.ts`). The code asking is the
|
|
24
|
+
app, so there is no key for an admin to hold or refresh.
|
|
25
|
+
|
|
26
|
+
**What this rules out:** a run that borrows an admin's session or key, and a run
|
|
27
|
+
that depends on a person to refresh a credential.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A scoped credential service is limited to the names its package declared
|
|
4
|
+
description: ScopedCredentialService refuses any credential name outside the package's declared set, filters getAll to that set, and refuses to enumerate users
|
|
5
|
+
tags: core, credentials, scoping
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A scoped credential service is limited to the names its package declared
|
|
9
|
+
|
|
10
|
+
`packages/core/src/services/scoped-credential-service.ts` wraps a
|
|
11
|
+
`CredentialService` with the set of names a package declared. The doc comment
|
|
12
|
+
says it "cannot reach a credential it never declared, or enumerate the app's
|
|
13
|
+
users" (`scoped-credential-service.ts:6-9`).
|
|
14
|
+
|
|
15
|
+
- `get`, `set`, `delete`, `has` and `getUsersWithCredential` each call
|
|
16
|
+
`assertAllowed` and throw `Access denied to credential` for an undeclared name
|
|
17
|
+
(`scoped-credential-service.ts:16-20`, `23`, `28`, `33`, `38`, `54`).
|
|
18
|
+
- `getAll(userId)` returns only the declared names that the user holds
|
|
19
|
+
(`scoped-credential-service.ts:42-51`).
|
|
20
|
+
- `getAllUsers` always throws (`scoped-credential-service.ts:58-62`).
|
|
21
|
+
|
|
22
|
+
Writes are allowed for declared names (`scoped-credential-service.ts:27-30`), per
|
|
23
|
+
the doc comment at `scoped-credential-service.ts:6-8`.
|
|
24
|
+
|
|
25
|
+
**What this rules out:** a package reading or writing another package's
|
|
26
|
+
credential, and a package listing every user of the app.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A scoped secret view reads only its declared keys and never writes
|
|
4
|
+
description: ScopedSecretService checks every read against an allowed-key set and refuses all writes, unlike the credential service that stays writable
|
|
5
|
+
tags: core, secrets, scoping
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A scoped secret view reads only its declared keys and never writes
|
|
9
|
+
|
|
10
|
+
`packages/core/src/services/scoped-secret-service.ts` wraps a `SecretService`
|
|
11
|
+
with a set of allowed keys (`scoped-secret-service.ts:4-8`).
|
|
12
|
+
|
|
13
|
+
Every read is checked first. `getSecret`, `hasSecret` and `getSecrets` each call
|
|
14
|
+
`assertAllowed`, which throws `Access denied to secret key` for any key outside
|
|
15
|
+
the set (`scoped-secret-service.ts:10-14`, `17`, `22`, `43`).
|
|
16
|
+
|
|
17
|
+
Every write is refused. `setSecret`, `setEncryptedSecret` and `deleteSecret`
|
|
18
|
+
throw unconditionally (`scoped-secret-service.ts:26-38`).
|
|
19
|
+
|
|
20
|
+
The credential counterpart is different. `scoped-credential-service.ts` says it
|
|
21
|
+
"stays writable — an addon refreshing its own OAuth token is the normal case"
|
|
22
|
+
(`scoped-credential-service.ts:6-8`).
|
|
23
|
+
|
|
24
|
+
**What this rules out:** an addon reading a secret it did not declare, and an
|
|
25
|
+
addon writing or deleting any secret through this view.
|