@pikku/core 0.12.103 → 0.12.106

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/dev/module-runner.js +33 -1
  3. package/dist/middleware/index.d.ts +1 -0
  4. package/dist/middleware/index.js +1 -0
  5. package/dist/middleware/remote-jobs-secret.d.ts +14 -0
  6. package/dist/middleware/remote-jobs-secret.js +27 -0
  7. package/dist/services/credential-wire-service.d.ts +15 -0
  8. package/dist/services/credential-wire-service.js +30 -11
  9. package/dist/services/index.d.ts +1 -0
  10. package/dist/services/index.js +1 -0
  11. package/dist/services/remote-jobs.d.ts +27 -0
  12. package/dist/services/remote-jobs.js +59 -0
  13. package/dist/services/webhook-service.d.ts +8 -0
  14. package/dist/wirings/scheduler/index.d.ts +1 -1
  15. package/dist/wirings/scheduler/index.js +1 -1
  16. package/dist/wirings/scheduler/scheduler-runner.d.ts +4 -0
  17. package/dist/wirings/scheduler/scheduler-runner.js +1 -1
  18. package/package.json +1 -1
  19. package/src/dev/module-runner.test.ts +23 -0
  20. package/src/dev/module-runner.ts +39 -1
  21. package/src/middleware/index.ts +1 -0
  22. package/src/middleware/remote-jobs-secret.ts +35 -0
  23. package/src/public-surface.json +61 -14
  24. package/src/services/credential-wire-service.test.ts +63 -0
  25. package/src/services/credential-wire-service.ts +27 -9
  26. package/src/services/index.ts +10 -0
  27. package/src/services/remote-jobs.test.ts +106 -0
  28. package/src/services/remote-jobs.ts +92 -0
  29. package/src/services/webhook-service.ts +8 -0
  30. package/src/wirings/scheduler/index.ts +1 -0
  31. package/src/wirings/scheduler/scheduler-runner.ts +1 -1
  32. package/tsconfig.tsbuildinfo +1 -1
  33. package/dist/classification/data-lock.d.ts +0 -80
  34. package/dist/classification/data-lock.js +0 -146
  35. package/dist/column-form.d.ts +0 -32
  36. package/dist/column-form.js +0 -42
  37. package/dist/data-classification.d.ts +0 -64
  38. package/dist/data-classification.js +0 -1
  39. package/dist/ecosystem/agent-scorer.d.ts +0 -12
  40. package/dist/ecosystem/agent-scorer.js +0 -5
  41. package/dist/ecosystem/agent.d.ts +0 -21
  42. package/dist/ecosystem/agent.js +0 -6
  43. package/dist/ecosystem/channel/local.d.ts +0 -3
  44. package/dist/ecosystem/channel/local.js +0 -3
  45. package/dist/ecosystem/channel/serverless.d.ts +0 -9
  46. package/dist/ecosystem/channel/serverless.js +0 -1
  47. package/dist/ecosystem/channel.d.ts +0 -15
  48. package/dist/ecosystem/channel.js +0 -5
  49. package/dist/ecosystem/cli/channel.d.ts +0 -16
  50. package/dist/ecosystem/cli/channel.js +0 -2
  51. package/dist/ecosystem/cli.d.ts +0 -9
  52. package/dist/ecosystem/cli.js +0 -2
  53. package/dist/ecosystem/credential.d.ts +0 -10
  54. package/dist/ecosystem/credential.js +0 -1
  55. package/dist/ecosystem/crypto-utils.d.ts +0 -8
  56. package/dist/ecosystem/crypto-utils.js +0 -1
  57. package/dist/ecosystem/dev.d.ts +0 -9
  58. package/dist/ecosystem/dev.js +0 -2
  59. package/dist/ecosystem/errors.d.ts +0 -8
  60. package/dist/ecosystem/errors.js +0 -2
  61. package/dist/ecosystem/function.d.ts +0 -13
  62. package/dist/ecosystem/function.js +0 -2
  63. package/dist/ecosystem/gateway.d.ts +0 -1
  64. package/dist/ecosystem/gateway.js +0 -1
  65. package/dist/ecosystem/hmac.d.ts +0 -1
  66. package/dist/ecosystem/hmac.js +0 -1
  67. package/dist/ecosystem/http.d.ts +0 -13
  68. package/dist/ecosystem/http.js +0 -5
  69. package/dist/ecosystem/mcp.d.ts +0 -3
  70. package/dist/ecosystem/mcp.js +0 -2
  71. package/dist/ecosystem/middleware.d.ts +0 -18
  72. package/dist/ecosystem/middleware.js +0 -7
  73. package/dist/ecosystem/node-host-resolver.d.ts +0 -1
  74. package/dist/ecosystem/node-host-resolver.js +0 -1
  75. package/dist/ecosystem/node.d.ts +0 -1
  76. package/dist/ecosystem/node.js +0 -1
  77. package/dist/ecosystem/oauth2.d.ts +0 -1
  78. package/dist/ecosystem/oauth2.js +0 -1
  79. package/dist/ecosystem/persona.d.ts +0 -17
  80. package/dist/ecosystem/persona.js +0 -5
  81. package/dist/ecosystem/queue.d.ts +0 -13
  82. package/dist/ecosystem/queue.js +0 -4
  83. package/dist/ecosystem/remote.d.ts +0 -8
  84. package/dist/ecosystem/remote.js +0 -1
  85. package/dist/ecosystem/role.d.ts +0 -4
  86. package/dist/ecosystem/role.js +0 -2
  87. package/dist/ecosystem/rpc.d.ts +0 -6
  88. package/dist/ecosystem/rpc.js +0 -3
  89. package/dist/ecosystem/safe-fetch.d.ts +0 -7
  90. package/dist/ecosystem/safe-fetch.js +0 -1
  91. package/dist/ecosystem/scenario.d.ts +0 -21
  92. package/dist/ecosystem/scenario.js +0 -8
  93. package/dist/ecosystem/scheduler.d.ts +0 -3
  94. package/dist/ecosystem/scheduler.js +0 -2
  95. package/dist/ecosystem/schema.d.ts +0 -2
  96. package/dist/ecosystem/schema.js +0 -1
  97. package/dist/ecosystem/scope.d.ts +0 -5
  98. package/dist/ecosystem/scope.js +0 -3
  99. package/dist/ecosystem/secret.d.ts +0 -11
  100. package/dist/ecosystem/secret.js +0 -2
  101. package/dist/ecosystem/services/istanbul-coverage.d.ts +0 -1
  102. package/dist/ecosystem/services/istanbul-coverage.js +0 -1
  103. package/dist/ecosystem/services/local-content-request-handler.d.ts +0 -9
  104. package/dist/ecosystem/services/local-content-request-handler.js +0 -1
  105. package/dist/ecosystem/services/local-content.d.ts +0 -2
  106. package/dist/ecosystem/services/local-content.js +0 -1
  107. package/dist/ecosystem/services/local-meta.d.ts +0 -1
  108. package/dist/ecosystem/services/local-meta.js +0 -1
  109. package/dist/ecosystem/services/v8-coverage.d.ts +0 -1
  110. package/dist/ecosystem/services/v8-coverage.js +0 -1
  111. package/dist/ecosystem/services.d.ts +0 -36
  112. package/dist/ecosystem/services.js +0 -12
  113. package/dist/ecosystem/testing.d.ts +0 -7
  114. package/dist/ecosystem/testing.js +0 -1
  115. package/dist/ecosystem/trigger.d.ts +0 -2
  116. package/dist/ecosystem/trigger.js +0 -1
  117. package/dist/ecosystem/types.d.ts +0 -12
  118. package/dist/ecosystem/types.js +0 -6
  119. package/dist/ecosystem/variable.d.ts +0 -9
  120. package/dist/ecosystem/variable.js +0 -1
  121. package/dist/ecosystem/virtual-user.d.ts +0 -24
  122. package/dist/ecosystem/virtual-user.js +0 -8
  123. package/dist/ecosystem/workflow.d.ts +0 -20
  124. package/dist/ecosystem/workflow.js +0 -6
  125. package/dist/ecosystem.d.ts +0 -27
  126. package/dist/ecosystem.js +0 -26
  127. package/dist/index.d.ts +0 -60
  128. package/dist/index.js +0 -25
  129. package/dist/middleware/require-unlocked.d.ts +0 -23
  130. package/dist/middleware/require-unlocked.js +0 -21
  131. package/dist/pikku-request.d.ts +0 -8
  132. package/dist/pikku-request.js +0 -15
  133. package/dist/secret-value.d.ts +0 -56
  134. package/dist/secret-value.js +0 -46
  135. package/dist/wirings/data-lock/data-lock-wiring.d.ts +0 -40
  136. package/dist/wirings/data-lock/data-lock-wiring.js +0 -77
  137. package/dist/wirings/data-lock/index.d.ts +0 -9
  138. package/dist/wirings/data-lock/index.js +0 -8
  139. package/dist/wirings/rpc/addon-runner.d.ts +0 -17
  140. package/dist/wirings/rpc/addon-runner.js +0 -150
  141. package/dist/wirings/rpc/remote-addon-auth.d.ts +0 -16
  142. package/dist/wirings/rpc/remote-addon-auth.js +0 -39
  143. package/dist/wirings/rpc/wire-addon.d.ts +0 -62
  144. package/dist/wirings/rpc/wire-addon.js +0 -88
  145. package/dist/wirings/rpc/wire-remote-addon.d.ts +0 -22
  146. package/dist/wirings/rpc/wire-remote-addon.js +0 -11
package/dist/index.js DELETED
@@ -1,25 +0,0 @@
1
- export { pikkuAgentMiddleware, pikkuChannelMiddleware, pikkuChannelMiddlewareFactory, pikkuMiddleware, pikkuMiddlewareFactory, } from './types/core.types.js';
2
- export { pikkuAuth, pikkuPermission, pikkuPermissionFactory, pikkuApprovalDescription, } from './function/functions.types.js';
3
- export { getAllFunctionNames } from './function/index.js';
4
- export { pikkuCLIRender } from './wirings/cli/cli-runner.js';
5
- export { PikkuRequest } from './pikku-request.js';
6
- export { getRelativeTimeOffsetFromNow, parseDurationString, } from './time-utils.js';
7
- export { formatVersionedId, isVersionedId, parseVersionedId, } from './version.js';
8
- export { AbandonedError } from './function/abort-scope.js';
9
- export { fetch } from './wirings/http/http-runner.js';
10
- export { AIProviderAuthError, AIProviderNotConfiguredError, BadGatewayError, BadRequestError, ConflictError, ExpectationFailedError, ForbiddenError, GatewayTimeoutError, GoneError, HTTPVersionNotSupportedError, InternalServerError, InvalidMiddlewareWireError, InvalidOriginError, InvalidSessionError, LengthRequiredError, LocalEnvironmentOnlyError, LockedError, MaxComputeTimeReachedError, MethodNotAllowedError, MissingCredentialError, MissingSchemaError, MissingScopeError, MissingServiceError, MissingSessionError, NotAcceptableError, NotFoundError, NotImplementedError, PayloadTooLargeError, PaymentRequiredError, PikkuMissingMetaError, PreconditionFailedError, ProxyAuthenticationRequiredError, RangeNotSatisfiableError, ReadonlySessionError, RequestTimeoutError, ServiceUnavailableError, TooManyRequestsError, URITooLongError, UnauthorizedError, UnprocessableContentError, UnsupportedMediaTypeError, } from './errors/errors.js';
11
- export { PikkuError, isExpectedError } from './errors/error-handler.js';
12
- export { SecretAccessDeniedError, withoutSecrets, } from './services/secretless.js';
13
- export { SecretHostNotAllowedError, assertSecretAllowedForHost, } from './services/secret-host-binding.js';
14
- export { NoopAuditService, createInvocationAudit, } from './services/audit-service.js';
15
- export { createGraph } from './wirings/workflow/graph/graph-node.js';
16
- export { wireAddon } from './wirings/rpc/wire-addon.js';
17
- export { wireRemoteAddon } from './wirings/rpc/wire-remote-addon.js';
18
- export { runMiddleware, addTagMiddleware } from './middleware-runner.js';
19
- export { addGlobalPermission, checkAuthPermissions } from './permissions.js';
20
- export { hasScopes, verifyScopes } from './scopes.js';
21
- export { isSerializable, stopSingletonServices, pikkuServerLifecycle, } from './utils.js';
22
- export { clearPikkuRuntimeState } from './test-utils.js';
23
- export { SchedulerService } from './services/scheduler-service.js';
24
- export { hashToken } from './column-form.js';
25
- export { createSecretValue, isSecretValue, SecretCoercionError, REDACTED, } from './secret-value.js';
@@ -1,23 +0,0 @@
1
- import type { DataLock } from '../classification/data-lock.js';
2
- /**
3
- * Refuses a request while the encrypted store is locked.
4
- *
5
- * Applied by tag or route rather than globally, because the unlock endpoint and
6
- * the static frontend have to stay reachable — a store that gated its own
7
- * unlock screen could never be opened. Static mounts serve a file hit before
8
- * dispatch, so the app shell is already outside this gate; the unlock function
9
- * is the one wiring that must not carry it.
10
- *
11
- * The gate is deliberately in front of the function rather than inside the
12
- * query layer. Both refuse, but only this one refuses before the handler has
13
- * touched the database.
14
- */
15
- export declare const requireUnlocked: (lock: DataLock) => import("./middleware.types.js").CorePikkuMiddleware<import("../types/core.types.js").CoreSingletonServices<{
16
- logLevel?: import("../services/logger.js").LogLevel;
17
- secrets?: {
18
- requireAllowedHosts?: boolean;
19
- };
20
- workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
21
- webhook?: import("../services/webhook-service.js").WebhookServiceConfig;
22
- postgres?: import("../types/core.types.js").PostgresConfig;
23
- }>, import("../types/core.types.js").CoreUserSession>;
@@ -1,21 +0,0 @@
1
- import { DataLockedError } from '../errors/errors.js';
2
- import { pikkuMiddleware } from './middleware-factories.js';
3
- /**
4
- * Refuses a request while the encrypted store is locked.
5
- *
6
- * Applied by tag or route rather than globally, because the unlock endpoint and
7
- * the static frontend have to stay reachable — a store that gated its own
8
- * unlock screen could never be opened. Static mounts serve a file hit before
9
- * dispatch, so the app shell is already outside this gate; the unlock function
10
- * is the one wiring that must not carry it.
11
- *
12
- * The gate is deliberately in front of the function rather than inside the
13
- * query layer. Both refuse, but only this one refuses before the handler has
14
- * touched the database.
15
- */
16
- export const requireUnlocked = (lock) => pikkuMiddleware(async (_services, _wires, next) => {
17
- if (lock.state !== 'unlocked') {
18
- throw new DataLockedError();
19
- }
20
- return next();
21
- });
@@ -1,8 +0,0 @@
1
- /**
2
- * @group RequestResponse
3
- */
4
- export declare abstract class PikkuRequest<In = any> {
5
- #private;
6
- constructor(data: In);
7
- data(): Promise<In>;
8
- }
@@ -1,15 +0,0 @@
1
- /**
2
- * @group RequestResponse
3
- */
4
- export class PikkuRequest {
5
- #data;
6
- constructor(data) {
7
- this.#data = data;
8
- }
9
- async data() {
10
- if (this.#data === undefined) {
11
- throw new Error('Data not found');
12
- }
13
- return this.#data;
14
- }
15
- }
@@ -1,56 +0,0 @@
1
- import type { Secret } from './data-classification.js';
2
- declare const secretValueBrand: unique symbol;
3
- export declare const REDACTED = "[secret]";
4
- /** Runtime marker, resilient to duplicate copies of core. */
5
- declare const SECRET_VALUE: unique symbol;
6
- declare const NODE_INSPECT: unique symbol;
7
- export declare class SecretCoercionError extends Error {
8
- constructor();
9
- }
10
- /**
11
- * A vault secret. Nominal, so it is not assignable to `string` and every
12
- * concretely-typed sink rejects it; `.reveal()` is the one way out, and every
13
- * call is a deliberate, greppable disclosure.
14
- *
15
- * The revealed value carries the erasable `Secret<T>` classification brand, so
16
- * the inspector can still follow it one hop past the call.
17
- *
18
- * Structured serialization redacts to `[secret]` — an audit or log write must
19
- * stay honest about the field without crashing the request. String coercion
20
- * throws, because a template literal or concatenation is always a leak.
21
- */
22
- export declare class SecretValue<T = string> {
23
- #private;
24
- readonly [secretValueBrand]: true;
25
- readonly [SECRET_VALUE] = true;
26
- constructor(value: T);
27
- reveal(): Secret<T>;
28
- toJSON(): string;
29
- [NODE_INSPECT](): string;
30
- toString(): never;
31
- [Symbol.toPrimitive](): never;
32
- }
33
- export declare const createSecretValue: <T>(value: T) => SecretValue<T>;
34
- export declare const isSecretValue: (value: unknown) => value is SecretValue<unknown>;
35
- type IsAny<T> = 0 extends 1 & T ? true : false;
36
- type Passthrough = Function | Date | RegExp | Error | ArrayBuffer | ArrayBufferView;
37
- /**
38
- * Rejects a `SecretValue` anywhere in `T`, however deeply nested, by collapsing
39
- * it to `never`.
40
- *
41
- * For sinks whose parameters are `any`, `unknown` or a free generic — loggers,
42
- * queue payloads, channel messages — where nominality alone cannot help.
43
- * `any` is passed through untouched: it cannot be guarded, and collapsing it
44
- * would reject every legitimate call.
45
- *
46
- * `Promise`, `Map` and `Set` are recursed into rather than passed through. A
47
- * mapped type cannot reach what they hold — mapping their keys yields their
48
- * methods, not their contents — so treating them as opaque let a secret ride
49
- * through inside one. `Promise` is the case that bites: `getSecret()` returns
50
- * `Promise<SecretValue<T>>`, so a forgotten `await` would otherwise log a
51
- * secret.
52
- */
53
- export type Safe<T> = IsAny<T> extends true ? T : [Extract<T, SecretValue<any>>] extends [never] ? T extends Promise<infer V> ? Promise<Safe<V>> : T extends Map<infer K, infer V> ? Map<Safe<K>, Safe<V>> : T extends Set<infer V> ? Set<Safe<V>> : T extends Passthrough ? T : T extends object ? {
54
- [K in keyof T]: Safe<T[K]>;
55
- } : T : never;
56
- export {};
@@ -1,46 +0,0 @@
1
- export const REDACTED = '[secret]';
2
- /** Runtime marker, resilient to duplicate copies of core. */
3
- const SECRET_VALUE = Symbol.for('pikku.secretValue');
4
- const NODE_INSPECT = Symbol.for('nodejs.util.inspect.custom');
5
- export class SecretCoercionError extends Error {
6
- constructor() {
7
- super(`A secret was coerced to a string, which would write it out in the clear. Unwrap it deliberately with .reveal() at the point it reaches the wire.`);
8
- this.name = 'SecretCoercionError';
9
- }
10
- }
11
- /**
12
- * A vault secret. Nominal, so it is not assignable to `string` and every
13
- * concretely-typed sink rejects it; `.reveal()` is the one way out, and every
14
- * call is a deliberate, greppable disclosure.
15
- *
16
- * The revealed value carries the erasable `Secret<T>` classification brand, so
17
- * the inspector can still follow it one hop past the call.
18
- *
19
- * Structured serialization redacts to `[secret]` — an audit or log write must
20
- * stay honest about the field without crashing the request. String coercion
21
- * throws, because a template literal or concatenation is always a leak.
22
- */
23
- export class SecretValue {
24
- [SECRET_VALUE] = true;
25
- #value;
26
- constructor(value) {
27
- this.#value = value;
28
- }
29
- reveal() {
30
- return this.#value;
31
- }
32
- toJSON() {
33
- return REDACTED;
34
- }
35
- [NODE_INSPECT]() {
36
- return REDACTED;
37
- }
38
- toString() {
39
- throw new SecretCoercionError();
40
- }
41
- [Symbol.toPrimitive]() {
42
- throw new SecretCoercionError();
43
- }
44
- }
45
- export const createSecretValue = (value) => new SecretValue(value);
46
- export const isSecretValue = (value) => typeof value === 'object' && value !== null && SECRET_VALUE in value;
@@ -1,40 +0,0 @@
1
- import type { DataLock, LockState } from '../../classification/data-lock.js';
2
- export type DataLockStatus = {
3
- state: LockState;
4
- /**
5
- * Milliseconds before another guess will be looked at, or 0.
6
- *
7
- * The unlock screen shows this as a countdown; without it the only way to
8
- * learn the wait is over is to guess again, and a guess made during a
9
- * lockout is itself a failure that extends it.
10
- */
11
- retryAfterMs: number;
12
- };
13
- export type DataLockWiringOptions = {
14
- /** Where the lock routes are mounted. Defaults to `/_pikku/data`. */
15
- prefix?: string;
16
- /**
17
- * Which keys first-run initialization mints. Derive it with
18
- * `keyIdsFromManifest`.
19
- *
20
- * It is fixed here rather than sent by the caller because the unlock screen
21
- * posts a passphrase and nothing else — and because a key the schema names
22
- * but nobody minted does not fail at startup, it fails at the first write to
23
- * that one column.
24
- */
25
- keyIds?: string[];
26
- };
27
- /**
28
- * Puts the passphrase gate on HTTP, so unlocking is a page in the app rather
29
- * than a prompt in whatever happens to have launched the server.
30
- *
31
- * That is what lets one story cover both shapes pikku ships in: a desktop
32
- * build whose window is pointed at the local server, and a headless
33
- * `pikku serve` somewhere else, unlock the same way and share the unlock
34
- * screen. A native prompt in the desktop shell would have left the headless
35
- * case with nothing.
36
- *
37
- * The routes are registered here rather than generated because they belong to
38
- * core: an app has no source file for them to be discovered in.
39
- */
40
- export declare const wireDataLock: (lock: DataLock, { prefix, keyIds }?: DataLockWiringOptions) => void;
@@ -1,77 +0,0 @@
1
- import { pikkuState } from '../../pikku-state.js';
2
- import { wireHTTP } from '../http/http-runner.js';
3
- import { httpRouter } from '../http/routers/http-router.js';
4
- const helperFunctionMeta = (funcId) => ({
5
- pikkuFuncId: funcId,
6
- sessionless: true,
7
- functionType: 'helper',
8
- inputSchemaName: null,
9
- outputSchemaName: null,
10
- });
11
- const DEFAULT_PREFIX = '/_pikku/data';
12
- /**
13
- * Puts the passphrase gate on HTTP, so unlocking is a page in the app rather
14
- * than a prompt in whatever happens to have launched the server.
15
- *
16
- * That is what lets one story cover both shapes pikku ships in: a desktop
17
- * build whose window is pointed at the local server, and a headless
18
- * `pikku serve` somewhere else, unlock the same way and share the unlock
19
- * screen. A native prompt in the desktop shell would have left the headless
20
- * case with nothing.
21
- *
22
- * The routes are registered here rather than generated because they belong to
23
- * core: an app has no source file for them to be discovered in.
24
- */
25
- export const wireDataLock = (lock, { prefix = DEFAULT_PREFIX, keyIds } = {}) => {
26
- const status = () => ({
27
- state: lock.state,
28
- retryAfterMs: lock.retryAfterMs,
29
- });
30
- register(prefix, 'get', '/status', 'pikkuDataLockStatus', async () => status());
31
- register(prefix, 'post', '/initialize', 'pikkuDataLockInitialize', async (_services, { passphrase }) => {
32
- await lock.initialize(passphrase, keyIds);
33
- return status();
34
- });
35
- register(prefix, 'post', '/unlock', 'pikkuDataLockUnlock', async (_services, { passphrase }) => {
36
- await lock.unlock(passphrase);
37
- return status();
38
- });
39
- register(prefix, 'post', '/lock', 'pikkuDataLockLock', async (_services, { passphrase }) => {
40
- // Locking proves ownership first. An open POST here would be a
41
- // one-request denial of service: the store shuts and stays shut until
42
- // someone is around to type the passphrase back in.
43
- await lock.unlock(passphrase);
44
- lock.lock();
45
- return status();
46
- });
47
- // A router that has already compiled its table would otherwise answer 404
48
- // for everything registered after it woke up.
49
- httpRouter.reset();
50
- };
51
- const register = (prefix, method, path, funcId, func) => {
52
- const route = `${prefix}${path}`;
53
- const routes = pikkuState(null, 'http', 'routes');
54
- if (routes.get(method)?.has(route)) {
55
- return;
56
- }
57
- const httpMeta = pikkuState(null, 'http', 'meta');
58
- httpMeta[method][route] = {
59
- pikkuFuncId: funcId,
60
- route,
61
- method,
62
- // Never a session. The gate cannot sit in front of its own key: a session
63
- // may itself live in a column this lock is holding shut.
64
- auth: false,
65
- requiresSession: false,
66
- };
67
- const functionsMeta = pikkuState(null, 'function', 'meta');
68
- if (!functionsMeta[funcId]) {
69
- functionsMeta[funcId] = helperFunctionMeta(funcId);
70
- }
71
- wireHTTP({
72
- method,
73
- route,
74
- func: { func },
75
- auth: false,
76
- });
77
- };
@@ -1,9 +0,0 @@
1
- /**
2
- * The HTTP face of {@link DataLock}: the routes an unlock screen talks to.
3
- *
4
- * Separate from `@pikku/core/classification` on purpose — that entry point is
5
- * types and crypto, and a runtime that never serves HTTP should not have to
6
- * load a router to use it.
7
- */
8
- export { wireDataLock } from './data-lock-wiring.js';
9
- export type { DataLockStatus, DataLockWiringOptions, } from './data-lock-wiring.js';
@@ -1,8 +0,0 @@
1
- /**
2
- * The HTTP face of {@link DataLock}: the routes an unlock screen talks to.
3
- *
4
- * Separate from `@pikku/core/classification` on purpose — that entry point is
5
- * types and crypto, and a runtime that never serves HTTP should not have to
6
- * load a router to use it.
7
- */
8
- export { wireDataLock } from './data-lock-wiring.js';
@@ -1,17 +0,0 @@
1
- import type { CoreSingletonServices } from '../../types/core.types.js';
2
- export type AddonInstance = {
3
- namespace: string;
4
- secretOverrides?: Record<string, string>;
5
- variableOverrides?: Record<string, string>;
6
- credentialOverrides?: Record<string, string>;
7
- /** Set by the consuming app: secrets it lends this instance, as the addon names them. */
8
- secretGrants?: string[];
9
- /** Set by the consuming app: credentials it lends this instance, as the addon names them. */
10
- credentialGrants?: string[];
11
- /** Set by the consuming app to opt this instance out of secret scoping. */
12
- globalSecrets?: string;
13
- /** Set by the consuming app to opt this instance out of credential scoping. */
14
- globalCredentials?: string;
15
- };
16
- export declare const getOrCreatePackageSingletonServices: (packageName: string, parentServices: CoreSingletonServices, addonInstance?: AddonInstance) => Promise<CoreSingletonServices>;
17
- export declare const addonInstanceForNamespace: (namespace: string | undefined, expectedPackage: string) => AddonInstance | undefined;
@@ -1,150 +0,0 @@
1
- import { pikkuState } from '../../pikku-state.js';
2
- import { ScopedSecretService } from '../../services/scoped-secret-service.js';
3
- import { ScopedCredentialService } from '../../services/scoped-credential-service.js';
4
- /**
5
- * What the addon declared, plus what the host lent it. Scoping runs outside the
6
- * aliaser, so every name here is the one the addon reads — which is why an
7
- * override's *key* grants, and its value does not.
8
- */
9
- const allowedNames = (declared, grants, overrides) => new Set([
10
- ...(declared ?? []),
11
- ...(grants ?? []),
12
- ...Object.keys(overrides ?? {}),
13
- ]);
14
- const aliasSecretService = (secrets, overrides) => {
15
- const map = (key) => overrides[key] ?? key;
16
- return {
17
- getSecret: (key) => secrets.getSecret(map(key)),
18
- hasSecret: (key) => secrets.hasSecret(map(key)),
19
- setSecret: (key, value) => secrets.setSecret(map(key), value),
20
- deleteSecret: (key) => secrets.deleteSecret(map(key)),
21
- getSecrets: async (keys) => {
22
- const result = await secrets.getSecrets(keys.map(map));
23
- const out = {};
24
- for (const logical of keys) {
25
- const real = map(logical);
26
- if (real in result)
27
- out[logical] = result[real];
28
- }
29
- return out;
30
- },
31
- };
32
- };
33
- const aliasVariablesService = (variables, overrides) => {
34
- const map = (name) => overrides[name] ?? name;
35
- return {
36
- get: (name) => variables.get(map(name)),
37
- getVariables: (names) => {
38
- const result = variables.getVariables(names.map(map));
39
- const remap = (r) => {
40
- const out = {};
41
- for (const logical of names) {
42
- const real = map(logical);
43
- if (real in r)
44
- out[logical] = r[real];
45
- }
46
- return out;
47
- };
48
- return result instanceof Promise
49
- ? result.then(remap)
50
- : remap(result);
51
- },
52
- getAll: () => variables.getAll(),
53
- set: (name, value) => variables.set(map(name), value),
54
- has: (name) => variables.has(map(name)),
55
- delete: (name) => variables.delete(map(name)),
56
- };
57
- };
58
- const findAddonNamespaceForPackage = (packageName) => {
59
- const addons = pikkuState(null, 'addons', 'packages');
60
- if (!addons)
61
- return null;
62
- for (const [namespace, cfg] of addons.entries()) {
63
- if (cfg?.package === packageName)
64
- return namespace;
65
- }
66
- return null;
67
- };
68
- const wrapWorkflowServiceForPackage = (service, packageName, namespace) => {
69
- return new Proxy(service, {
70
- get(target, prop, receiver) {
71
- if (prop === 'startWorkflow' || prop === 'runToCompletion') {
72
- const original = Reflect.get(target, prop, receiver);
73
- return function (name, ...rest) {
74
- if (typeof name === 'string' && !name.includes(':')) {
75
- const ns = namespace ?? findAddonNamespaceForPackage(packageName);
76
- if (ns) {
77
- name = `${ns}:${name}`;
78
- }
79
- }
80
- return original.call(this, name, ...rest);
81
- };
82
- }
83
- return Reflect.get(target, prop, receiver);
84
- },
85
- });
86
- };
87
- export const getOrCreatePackageSingletonServices = async (packageName, parentServices, addonInstance) => {
88
- const cacheKey = addonInstance?.namespace ?? packageName;
89
- const cachedServices = pikkuState(cacheKey, 'package', 'singletonServices');
90
- if (cachedServices) {
91
- return cachedServices;
92
- }
93
- const factories = pikkuState(packageName, 'package', 'factories');
94
- let existingServices = parentServices;
95
- if (addonInstance?.secretOverrides && parentServices.secrets) {
96
- existingServices = {
97
- ...existingServices,
98
- secrets: aliasSecretService(parentServices.secrets, addonInstance.secretOverrides),
99
- };
100
- }
101
- if (addonInstance?.variableOverrides && parentServices.variables) {
102
- existingServices = {
103
- ...existingServices,
104
- variables: aliasVariablesService(parentServices.variables, addonInstance.variableOverrides),
105
- };
106
- }
107
- if (!addonInstance?.globalSecrets && existingServices.secrets) {
108
- existingServices = {
109
- ...existingServices,
110
- secrets: new ScopedSecretService(existingServices.secrets, allowedNames(pikkuState(packageName, 'package', 'declaredSecrets'), addonInstance?.secretGrants, addonInstance?.secretOverrides)),
111
- };
112
- }
113
- if (!addonInstance?.globalCredentials && existingServices.credentialService) {
114
- existingServices = {
115
- ...existingServices,
116
- credentialService: new ScopedCredentialService(existingServices.credentialService, allowedNames(Object.keys(pikkuState(packageName, 'package', 'credentialsMeta') ?? {}), addonInstance?.credentialGrants, addonInstance?.credentialOverrides)),
117
- };
118
- }
119
- if (!factories || !factories.createSingletonServices) {
120
- return existingServices;
121
- }
122
- let config = existingServices.config;
123
- if (factories.createConfig) {
124
- config = await factories.createConfig(existingServices.variables);
125
- }
126
- const packageServices = await factories.createSingletonServices(config, existingServices);
127
- if (packageServices.workflowService &&
128
- typeof packageServices.workflowService === 'object') {
129
- packageServices.workflowService = wrapWorkflowServiceForPackage(packageServices.workflowService, packageName, addonInstance?.namespace ?? null);
130
- }
131
- pikkuState(cacheKey, 'package', 'singletonServices', packageServices);
132
- return packageServices;
133
- };
134
- export const addonInstanceForNamespace = (namespace, expectedPackage) => {
135
- if (!namespace)
136
- return undefined;
137
- const cfg = pikkuState(null, 'addons', 'packages').get(namespace);
138
- if (!cfg || cfg.package !== expectedPackage)
139
- return undefined;
140
- return {
141
- namespace,
142
- secretOverrides: cfg.secretOverrides,
143
- variableOverrides: cfg.variableOverrides,
144
- credentialOverrides: cfg.credentialOverrides,
145
- secretGrants: cfg.secretGrants,
146
- credentialGrants: cfg.credentialGrants,
147
- globalSecrets: cfg.globalSecrets,
148
- globalCredentials: cfg.globalCredentials,
149
- };
150
- };
@@ -1,16 +0,0 @@
1
- import type { CoreServices, PikkuRawWire } from '../../types/core.types.js';
2
- import { PikkuError } from '../../errors/error-handler.js';
3
- import { type SecretValue } from '../../classification/secret-value.js';
4
- export type RemoteAddonAuthBinding = {
5
- credentialId: string;
6
- } | {
7
- secretId: string;
8
- } | {
9
- resolve: (services: CoreServices, wire: PikkuRawWire) => string | SecretValue<string> | Promise<string | SecretValue<string>>;
10
- };
11
- export declare class RemoteAddonAuthError extends PikkuError {
12
- readonly namespace: string;
13
- constructor(namespace: string, detail: string);
14
- }
15
- /** Returns `null` only when no auth is bound; an empty resolved token throws. */
16
- export declare function resolveRemoteAddonToken(auth: RemoteAddonAuthBinding | undefined, services: CoreServices, wire: PikkuRawWire, namespace: string): Promise<string | null>;
@@ -1,39 +0,0 @@
1
- import { PikkuError, addError } from '../../errors/error-handler.js';
2
- import { isSecretValue, } from '../../classification/secret-value.js';
3
- export class RemoteAddonAuthError extends PikkuError {
4
- namespace;
5
- constructor(namespace, detail) {
6
- super(`Remote addon '${namespace}' auth could not be resolved: ${detail}`);
7
- this.namespace = namespace;
8
- }
9
- }
10
- addError(RemoteAddonAuthError, {
11
- status: 401,
12
- message: 'Remote addon authentication could not be resolved.',
13
- });
14
- /** Returns `null` only when no auth is bound; an empty resolved token throws. */
15
- export async function resolveRemoteAddonToken(auth, services, wire, namespace) {
16
- if (!auth) {
17
- return null;
18
- }
19
- let token;
20
- if ('credentialId' in auth) {
21
- if (typeof wire.getCredential !== 'function') {
22
- throw new RemoteAddonAuthError(namespace, `credentialId '${auth.credentialId}' requires a wire with credential access`);
23
- }
24
- token = await wire.getCredential(auth.credentialId);
25
- }
26
- else if ('secretId' in auth) {
27
- token = await services.secrets.getSecret(auth.secretId);
28
- }
29
- else if (typeof auth.resolve === 'function') {
30
- token = await auth.resolve(services, wire);
31
- }
32
- // The wire is where a secret is meant to end up, so unwrap it here rather
33
- // than at each branch — `resolve` and `getCredential` may hand one back too.
34
- const resolved = isSecretValue(token) ? token.reveal() : token;
35
- if (resolved === null || resolved === undefined || resolved === '') {
36
- throw new RemoteAddonAuthError(namespace, 'resolved token was empty');
37
- }
38
- return String(resolved);
39
- }
@@ -1,62 +0,0 @@
1
- import type { CorePikkuMiddleware } from '../../middleware/middleware.types.js';
2
- export type WireAddonConfig = {
3
- name: string;
4
- package: string;
5
- rpcEndpoint?: string;
6
- auth?: boolean;
7
- mcp?: boolean;
8
- tags?: string[];
9
- /** Required of every function in the addon, on top of the function's own. */
10
- scopes?: string[];
11
- secretOverrides?: Record<string, string>;
12
- variableOverrides?: Record<string, string>;
13
- credentialOverrides?: Record<string, string>;
14
- /**
15
- * Secrets this instance may read on top of the ones it declared, named as the
16
- * addon reads them — the scope check runs before `secretOverrides` renames
17
- * them, so an overridden secret is named here by its addon-side key. Listing
18
- * one in `secretOverrides` grants it too.
19
- *
20
- * For an addon whose secret names come off its input rather than its own
21
- * source, this is how the host lends names the addon could not declare.
22
- */
23
- secretGrants?: string[];
24
- /** Credentials this instance may read on top of the ones it declared. */
25
- credentialGrants?: string[];
26
- /**
27
- * Hands this instance the whole `SecretService` instead of one scoped to the
28
- * secrets it declared. The value is the reason, recorded in the deploy
29
- * manifest — an addon that names secrets at runtime cannot be scoped, and
30
- * only the consuming app, never the addon, can grant it.
31
- */
32
- globalSecrets?: string;
33
- /**
34
- * Hands this instance the whole `CredentialService` instead of one narrowed
35
- * to the credentials it declared. The value is the reason, recorded in the
36
- * deploy manifest, and only the consuming app can grant it.
37
- */
38
- globalCredentials?: string;
39
- };
40
- export declare const wireAddon: (config: WireAddonConfig) => void;
41
- /**
42
- * knowledge: decisions/security/addon-scopes-are-resolved-where-the-function-runs.md
43
- */
44
- export declare const resolveAddonScopes: (packageName: string | null, namespace?: string) => string[];
45
- /**
46
- * knowledge: decisions/security/addon-auth-and-tags-only-tighten.md
47
- */
48
- export declare const resolveAddonTags: (packageName: string | null, namespace?: string) => string[];
49
- /**
50
- * True when any governing instance requires a session. `auth: false` is not
51
- * honoured here — on a direct wiring it would weaken the wiring's own gate.
52
- *
53
- * knowledge: decisions/security/addon-auth-and-tags-only-tighten.md
54
- */
55
- export declare const resolveAddonAuth: (packageName: string | null, namespace?: string) => boolean;
56
- /**
57
- * Addon tags name middleware the *consuming app* registered, so they resolve
58
- * against the root tag groups rather than the addon package's own.
59
- *
60
- * knowledge: decisions/security/addon-auth-and-tags-only-tighten.md
61
- */
62
- export declare const resolveAddonTagMiddleware: (packageName: string | null, namespace?: string) => CorePikkuMiddleware[];