@hasna/workflows 0.1.4 → 0.2.1

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.
@@ -3,8 +3,8 @@
3
3
  "name": "workflows",
4
4
  "class": "service",
5
5
  "contractVersion": "v1",
6
- "kitVersion": "0.14.2",
7
- "description": "Universal graph workflow app: graph language (start/step/decision/while/end), three-table run store, session WAL, claims/leases daemon, four lane adapters (claude, codex, cursor, grok), and a full CLI on four surfaces (CLI, MCP, serve, SDK). Local state lives at the effective workflows data home (~/.hasna/workflows by default, resolved via @hasna/paths; the XDG data home is adopted once the store is migrated there or HASNA_DATA_HOME is set).",
6
+ "kitVersion": "1.3.3",
7
+ "description": "Hosted workflow CLI, MCP and HTTP SDK, with a PostgreSQL execution server and a preserved session journal.",
8
8
  "bins": [
9
9
  "workflows",
10
10
  "workflows-mcp",
@@ -19,7 +19,7 @@
19
19
  "kind": "api",
20
20
  "status": "supported",
21
21
  "bin": "workflows-serve",
22
- "authMode": "local-only",
22
+ "authMode": "api-key",
23
23
  "health": {
24
24
  "method": "GET",
25
25
  "path": "/health",
@@ -35,41 +35,41 @@
35
35
  "path": "/version",
36
36
  "public": true
37
37
  },
38
- "openApiPath": "/openapi.json"
38
+ "openApiPath": "/v1/openapi.json"
39
39
  },
40
40
  {
41
41
  "name": "typescript-sdk",
42
42
  "kind": "sdk",
43
43
  "status": "supported",
44
- "authMode": "local-only",
44
+ "authMode": "api-key",
45
45
  "exportSubpath": "./sdk",
46
- "clientClassName": "WorkflowsService",
47
- "generatedFrom": "/openapi.json"
46
+ "clientClassName": "WorkflowsClient",
47
+ "generatedFrom": "/v1/openapi.json"
48
48
  },
49
49
  {
50
50
  "name": "mcp",
51
51
  "kind": "mcp",
52
52
  "status": "supported",
53
53
  "mcpBin": "workflows-mcp",
54
- "authMode": "local-only"
54
+ "authMode": "api-key"
55
55
  },
56
56
  {
57
57
  "name": "cli",
58
58
  "kind": "cli",
59
59
  "status": "supported",
60
60
  "bin": "workflows",
61
- "authMode": "local-only"
61
+ "authMode": "api-key"
62
62
  }
63
63
  ],
64
64
  "metadata": {
65
65
  "serve": {
66
66
  "healthEndpoints": [
67
- "/health",
68
- "/ready",
69
- "/version"
67
+ "/v1/health",
68
+ "/v1/ready",
69
+ "/v1/version"
70
70
  ],
71
- "openapi": "/openapi.json",
72
- "auth": "local-only"
71
+ "openapi": "/v1/openapi.json",
72
+ "auth": "api-key"
73
73
  },
74
74
  "release": {
75
75
  "artifactScan": {
@@ -78,10 +78,10 @@
78
78
  }
79
79
  },
80
80
  "storage": {
81
- "backend": "sqlite",
81
+ "backend": "postgresql",
82
82
  "engines": [
83
- "sqlite",
84
- "postgresql"
83
+ "postgresql",
84
+ "sqlite"
85
85
  ],
86
86
  "envPrefix": "HASNA_WORKFLOWS_",
87
87
  "aliasEnvPrefix": "WORKFLOWS_",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/workflows",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "Universal graph workflow app — graph language (start/step/decision/while/end), three-table store, session WAL, daemon with claims/leases, four lane adapters (claude/codex/cursor/grok), and a full CLI on four surfaces",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -16,8 +16,8 @@
16
16
  "import": "./dist/index.js"
17
17
  },
18
18
  "./sdk": {
19
- "types": "./types/index.d.ts",
20
- "import": "./dist/index.js"
19
+ "types": "./types/sdk.d.ts",
20
+ "import": "./dist/sdk.js"
21
21
  },
22
22
  "./cli": {
23
23
  "types": "./types/cli/index.d.ts",
@@ -32,14 +32,17 @@
32
32
  "hasna.contract.json"
33
33
  ],
34
34
  "scripts": {
35
- "build": "bun run build:runtime && bun run build:types",
36
- "build:runtime": "rm -rf dist && bun build src/cli/index.ts --outdir dist/cli --target bun --packages external && bun build src/mcp/index.ts --outdir dist/mcp --target bun --packages external && bun build src/serve/index.ts --outdir dist/serve --target bun --packages external && bun build src/index.ts src/service.ts src/handlers.ts src/agent-tools.ts src/types.ts --root src --outdir dist --target bun --packages external",
37
- "build:types": "rm -rf types && tsc -p tsconfig.build.json --emitDeclarationOnly --outDir types",
35
+ "build": "bun run check:sdk && bun run build:runtime && bun run build:types",
36
+ "generate:sdk": "bun scripts/generate-sdk.ts --write",
37
+ "check:sdk": "bun scripts/generate-sdk.ts",
38
+ "test:artifacts": "bun scripts/artifact-acceptance.ts",
39
+ "build:runtime": "bun build src/cli/index.ts --outdir dist/cli --target bun && bun build src/mcp/index.ts --outdir dist/mcp --target bun && bun build src/serve/index.ts src/serve/admin.ts --outdir dist/serve --target bun --packages external && bun build src/index.ts src/service.ts src/handlers.ts src/agent-tools.ts src/types.ts --root src --outdir dist --target bun --packages external && bun build src/sdk.ts --outdir dist --target node",
40
+ "build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly --outDir types",
38
41
  "artifact-scan": "bun scripts/contracts-cli.mjs artifact-scan dist",
39
42
  "contract:check": "bun scripts/contracts-cli.mjs repo-conformance .",
40
43
  "typecheck": "tsc --noEmit",
41
44
  "test": "bun test",
42
- "test:pg": "WORKFLOWS_TEST_DATABASE_URL=\"${WORKFLOWS_TEST_DATABASE_URL:?point it at a throwaway Postgres; the live-PG gate must not pass without one}\" WORKFLOWS_REQUIRE_POSTGRES=1 bun test src/db/pg-live.test.ts",
45
+ "test:pg": "WORKFLOWS_TEST_DATABASE_URL=\"${WORKFLOWS_TEST_DATABASE_URL:?point it at a fresh test Postgres database}\" WORKFLOWS_REQUIRE_POSTGRES=1 bun test src/db/postgres-store.test.ts src/db/postgres-http.test.ts src/db/postgres-fencing.test.ts",
43
46
  "prepack": "bun run build && bun run artifact-scan",
44
47
  "prepublishOnly": "bun run test && bun run build"
45
48
  },
@@ -58,7 +61,8 @@
58
61
  },
59
62
  "repository": {
60
63
  "type": "git",
61
- "url": "git+https://github.com/hasna/apps.git"
64
+ "url": "https://github.com/hasna/apps.git",
65
+ "directory": "apps/workflows"
62
66
  },
63
67
  "homepage": "https://github.com/hasna/apps/tree/main/apps/workflows",
64
68
  "bugs": {
@@ -72,15 +76,14 @@
72
76
  "dependencies": {
73
77
  "@anthropic-ai/claude-agent-sdk": "0.3.234",
74
78
  "@cursor/sdk": "^1.0.28",
75
- "@hasna/paths": "0.1.0",
76
- "@openai/codex-sdk": "0.147.0",
77
- "commander": "^13.1.0"
79
+ "@hasna/contracts": "1.3.3",
80
+ "@openai/codex-sdk": "0.160.0",
81
+ "commander": "^13.1.0",
82
+ "pg": "^8.16.3"
78
83
  },
79
84
  "devDependencies": {
80
- "@hasna/contracts": "0.14.2",
81
85
  "@types/bun": "1.3.14",
82
86
  "@types/pg": "^8.15.6",
83
- "pg": "^8.16.3",
84
87
  "typescript": "^5.7.3"
85
88
  }
86
89
  }
@@ -1,19 +1,5 @@
1
1
  import type { WorkflowsService } from "./service.js";
2
- export interface McpTool {
3
- name: string;
4
- description: string;
5
- inputSchema: {
6
- type: "object";
7
- properties: Record<string, unknown>;
8
- required?: string[];
9
- };
10
- }
11
- export interface ToolCallResult {
12
- content: {
13
- type: "text";
14
- text: string;
15
- }[];
16
- isError: boolean;
17
- }
18
- export declare function workflowsTools(_service: WorkflowsService): McpTool[];
2
+ export { workflowsTools } from "./tool-definitions.js";
3
+ export type { McpTool, ToolCallResult } from "./tool-definitions.js";
4
+ import type { ToolCallResult } from "./tool-definitions.js";
19
5
  export declare function callWorkflowTool(service: WorkflowsService, name: string, args: Record<string, unknown>): Promise<ToolCallResult>;
@@ -0,0 +1,4 @@
1
+ /** Only authored diagnostics and known scalar codes may reach a CLI or MCP transcript. */
2
+ export declare class WorkflowInputError extends Error {
3
+ }
4
+ export declare function workflowErrorMessage(error: unknown): string;
@@ -0,0 +1,4 @@
1
+ import type { WorkflowsClient } from "./client.js";
2
+ import { type ToolCallResult } from "./tool-definitions.js";
3
+ /** MCP delegates data operations to the same hosted client as the CLI and SDK. */
4
+ export declare function callClientTool(client: () => WorkflowsClient, name: string, args: Record<string, unknown>): Promise<ToolCallResult>;
@@ -0,0 +1,60 @@
1
+ /** Hosted client shared by CLI, MCP and SDK. Never opens an application store. */
2
+ import { type ClientTransportResolution } from "@hasna/contracts/client";
3
+ import type { WorkflowGraph } from "./graph.js";
4
+ import type { RunRow, RunNodeRow, RunStatus } from "./store.js";
5
+ import type { HealthReport, ReadinessReport } from "./types.js";
6
+ import { WorkflowsApi } from "./sdk/generated-api.js";
7
+ export interface WorkflowsClientOptions {
8
+ apiUrl?: string;
9
+ apiKey?: string;
10
+ env?: Record<string, string | undefined>;
11
+ fetchImpl?: typeof fetch;
12
+ timeoutMs?: number;
13
+ agent?: string;
14
+ }
15
+ export interface RunSummary {
16
+ runId: string;
17
+ status: string;
18
+ error: string | null;
19
+ result: unknown;
20
+ reused: boolean;
21
+ }
22
+ export declare class WorkflowsClient extends WorkflowsApi {
23
+ readonly name = "workflows";
24
+ readonly resolution: ClientTransportResolution;
25
+ private readonly http;
26
+ constructor(options?: WorkflowsClientOptions);
27
+ request<T>(method: string, path: string, body?: unknown): Promise<T>;
28
+ protected apiRequest<T>(method: string, path: string, opts: {
29
+ body?: unknown;
30
+ query?: Record<string, unknown>;
31
+ }): Promise<T>;
32
+ health(): Promise<HealthReport>;
33
+ ready(): Promise<ReadinessReport>;
34
+ version(): Promise<{
35
+ service: string;
36
+ version: string;
37
+ }>;
38
+ validate(graph: unknown): Promise<{
39
+ ok: boolean;
40
+ issues: {
41
+ path: string;
42
+ message: string;
43
+ }[];
44
+ }>;
45
+ triggerRun(graph: WorkflowGraph, context?: unknown, opts?: {
46
+ maxCycles?: number;
47
+ idempotencyKey?: string;
48
+ }): Promise<RunSummary>;
49
+ listRuns(options?: {
50
+ status?: RunStatus;
51
+ limit?: number;
52
+ }): Promise<RunRow[]>;
53
+ getRun(id: string): Promise<RunRow>;
54
+ listNodes(id: string): Promise<RunNodeRow[]>;
55
+ events(id: string): Promise<unknown[]>;
56
+ cancelRun(id: string): Promise<RunRow>;
57
+ resumeRun(id: string): Promise<RunSummary>;
58
+ lanes(): Promise<unknown[]>;
59
+ }
60
+ export declare function createWorkflowsClient(options?: WorkflowsClientOptions): WorkflowsClient;
package/types/daemon.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type WorkflowGraph } from "./graph.js";
2
- import type { RunRow, WorkflowsStore } from "./store.js";
2
+ import type { RunRow, WorkflowsStore, EngineStore } from "./store.js";
3
3
  import { SessionWAL } from "./wal.js";
4
4
  import { type LaneJob, type LaneResult } from "./lanes/types.js";
5
5
  export type { LaneJob, LaneKind, LaneResult } from "./lanes/types.js";
@@ -50,8 +50,8 @@ export type StepOutcome = {
50
50
  kind: "blocked";
51
51
  };
52
52
  /** Execute a shell command with a bounded timeout (the built-in step executor). */
53
- export declare function runCommand(job: LaneJob): LaneResult;
54
- export declare class WorkflowsDaemon {
53
+ export declare function runCommand(job: LaneJob): Promise<LaneResult>;
54
+ export declare class WorkflowsDaemon<S extends EngineStore = WorkflowsStore> {
55
55
  private readonly store;
56
56
  private readonly wal;
57
57
  readonly worker: string;
@@ -66,7 +66,7 @@ export declare class WorkflowsDaemon {
66
66
  private readonly fencingCounter;
67
67
  private graphCache;
68
68
  private isolatedRunId;
69
- constructor(store: WorkflowsStore, wal: SessionWAL, options?: DaemonOptions);
69
+ constructor(store: S, wal: SessionWAL, options?: DaemonOptions);
70
70
  /** Foreground single-run mode: reap() touches only this run from now on. */
71
71
  isolateRun(runId: string): void;
72
72
  /**
@@ -82,25 +82,29 @@ export declare class WorkflowsDaemon {
82
82
  /** Acquire (or refresh) the lease on a run. Returns null while another
83
83
  * worker's lease is live. */
84
84
  claim(runId: string): Lease | null;
85
+ private claimLocked;
85
86
  /** Extend a lease. Only the fencing-holding worker may. */
86
87
  heartbeat(runId: string, fencing: number): boolean;
88
+ private heartbeatLocked;
87
89
  /** Release a lease. Only the fencing-holding worker may. */
88
90
  release(runId: string, fencing: number): boolean;
91
+ private releaseLocked;
89
92
  /** One bounded cycle: expire leases -> repair torn runs -> dispatch pending
90
93
  * runs -> advance every claimed run one step. */
91
94
  reap(): Promise<ReapReport>;
92
95
  /** Validate + persist a new run. Returns the pending run row. */
93
- startRun(graph: WorkflowGraph, context: unknown): RunRow;
96
+ startRun(graph: WorkflowGraph, context: unknown): ReturnType<S["createRun"]>;
94
97
  /** Execute ONE step of the graph for a claimed run. */
95
98
  advanceRun(runId: string): Promise<StepOutcome>;
96
99
  private loadGraph;
97
100
  /** The condition scope: user context + steps + loop counter (i). */
98
101
  private conditionScope;
99
102
  private persistContext;
103
+ private discardStep;
100
104
  private executeStep;
101
- /** Advance past a finished step node. Inside a while body with no next,
102
- * the iteration completes and the while node re-enters. */
103
- private afterNode;
105
+ /** Prepare continuation before commitStep so output, cursor and loop counts
106
+ * share one transaction. Cancellation retains all of them or rejects all. */
107
+ private advanceAfterStep;
104
108
  private executeWhile;
105
109
  private recordStepResult;
106
110
  private completeRun;
@@ -117,6 +121,8 @@ export interface RunToCompletionOptions {
117
121
  /** Observe the durable run identity immediately after creation (the CLI -j
118
122
  * failure surface needs the runId even when the run later throws). */
119
123
  onRunCreated?: (run: RunRow) => void;
124
+ /** Internal continuation of an existing durable run. */
125
+ existingRunId?: string;
120
126
  }
121
127
  /** Drive a run to a terminal state with bounded cycles. Returns the final row. */
122
- export declare function runGraphToCompletion(store: WorkflowsStore, wal: SessionWAL, graph: WorkflowGraph, context: unknown, options?: RunToCompletionOptions): Promise<RunRow>;
128
+ export declare function runGraphToCompletion(store: EngineStore, wal: SessionWAL, graph: WorkflowGraph, context: unknown, options?: RunToCompletionOptions): Promise<RunRow>;
@@ -0,0 +1,54 @@
1
+ import type { WorkflowsStore, RunRow, RunNodeRow, MemoRow, CreateRunInput, CreateRunNodeInput, StepCommit, DispatchResult } from "../store.js";
2
+ export type AsyncWorkflowsStore = {
3
+ [K in keyof WorkflowsStore]: WorkflowsStore[K] extends (...args: infer A) => infer R ? (...args: A) => Promise<R> : WorkflowsStore[K];
4
+ };
5
+ /** Safe diagnostic: driver errors may carry connection details and are never rendered. */
6
+ export declare class WorkflowStorageError extends Error {
7
+ constructor();
8
+ }
9
+ export declare class PostgresWorkflowsStore implements AsyncWorkflowsStore {
10
+ private readonly pool;
11
+ private readonly prefix;
12
+ readonly dataDir: string;
13
+ private execution?;
14
+ constructor(options: {
15
+ databaseUrl: string;
16
+ dataDir: string;
17
+ schema?: string;
18
+ });
19
+ private rows;
20
+ close(): Promise<void>;
21
+ /** Session advisory lock is shared by every executor, including separate journals.
22
+ * All owner writes use its exact connection; a lost connection never falls back. */
23
+ acquireExecution(runId: string): Promise<boolean>;
24
+ releaseExecution(): Promise<void>;
25
+ private transaction;
26
+ listTables(): Promise<string[]>;
27
+ checkSchema(): Promise<boolean>;
28
+ /** Creation-only bootstrap. Operators admit this explicitly; reads never migrate. */
29
+ initialize(): Promise<void>;
30
+ createRun(input: CreateRunInput): Promise<RunRow>;
31
+ getRun(id: string): Promise<RunRow | undefined>;
32
+ getRunByIdempotencyKey(key: string): Promise<RunRow | undefined>;
33
+ listRuns(options?: Parameters<WorkflowsStore["listRuns"]>[0]): Promise<RunRow[]>;
34
+ setRunStatus(id: string, status: RunRow["status"], patch?: NonNullable<Parameters<WorkflowsStore["setRunStatus"]>[2]>): Promise<void>;
35
+ compareRunStatus(id: string, expected: RunRow["status"][], status: RunRow["status"], patch?: Parameters<WorkflowsStore["setRunStatus"]>[2]): Promise<boolean>;
36
+ setRunContext(id: string, context: unknown): Promise<void>;
37
+ setRunningContext(id: string, context: unknown): Promise<boolean>;
38
+ blockRunResume(id: string): Promise<void>;
39
+ dispatchIfRunning<T>(id: string, action: () => T): Promise<DispatchResult<T>>;
40
+ finishDispatch(id: string, unconfirmed: boolean): Promise<void>;
41
+ commitStep(input: StepCommit): Promise<boolean>;
42
+ bumpAttempts(id: string): Promise<void>;
43
+ createRunNode(input: CreateRunNodeInput): Promise<RunNodeRow>;
44
+ getRunNode(id: string): Promise<RunNodeRow | undefined>;
45
+ listRunNodes(runId: string): Promise<RunNodeRow[]>;
46
+ getLatestRunNode(runId: string, nodeId: string): Promise<RunNodeRow | undefined>;
47
+ setRunNodeStatus(id: string, status: RunNodeRow["status"], patch?: NonNullable<Parameters<WorkflowsStore["setRunNodeStatus"]>[2]>): Promise<void>;
48
+ bumpAttemptsNode(id: string): Promise<void>;
49
+ memoPut(key: string, graphName: string, nodeId: string, inputHash: string, outputJson: string): Promise<void>;
50
+ memoGet(key: string): Promise<MemoRow | undefined>;
51
+ memoHit(key: string): Promise<void>;
52
+ memoList(): Promise<MemoRow[]>;
53
+ memoClear(): Promise<void>;
54
+ }
package/types/graph.d.ts CHANGED
@@ -2,6 +2,8 @@ export declare const GRAPH_NODE_TYPES: readonly ["start", "step", "decision", "w
2
2
  export type GraphNodeType = (typeof GRAPH_NODE_TYPES)[number];
3
3
  export declare const LANE_KINDS: readonly ["claude", "codex", "cursor", "grok"];
4
4
  export type LaneKind = (typeof LANE_KINDS)[number];
5
+ export declare const CODEX_EXECUTION_MODES: readonly ["repository", "read-only"];
6
+ export type CodexExecutionMode = (typeof CODEX_EXECUTION_MODES)[number];
5
7
  export interface StartNode {
6
8
  id: string;
7
9
  type: "start";
@@ -16,6 +18,10 @@ export interface StepNode {
16
18
  prompt?: string;
17
19
  /** Shell command alternative to a lane prompt. */
18
20
  command?: string;
21
+ /** Working directory on the executor, shared by shell and model lanes. */
22
+ cwd?: string;
23
+ /** Codex prompts only. Read-only permits nonrepository work without writes or approvals. */
24
+ codexMode?: CodexExecutionMode;
19
25
  next?: string;
20
26
  /** Retries on lane failure. Default 0. */
21
27
  maxRetries?: number;
@@ -1,3 +1,4 @@
1
- import type { WorkflowsService } from "./service.js";
1
+ import { type WorkflowsService } from "./service.js";
2
2
  export declare function jsonResponse(body: unknown, status?: number): Response;
3
+ export declare function openApiDoc(service: WorkflowsService): Record<string, unknown>;
3
4
  export declare function createRequestHandler(service: WorkflowsService): (req: Request) => Response | Promise<Response>;
@@ -0,0 +1,7 @@
1
+ export type IdentityEnvironment = Record<string, string | undefined>;
2
+ export interface WorkflowAgent {
3
+ name: string;
4
+ source: string;
5
+ }
6
+ export declare function workflowAgent(env?: IdentityEnvironment, explicit?: string): WorkflowAgent;
7
+ export declare function workflowMcpAgent(env: IdentityEnvironment, harness: string, explicit?: unknown, meta?: unknown): WorkflowAgent;
@@ -1,9 +1,3 @@
1
- /**
2
- * codex lane adapter — @openai/codex-sdk.
3
- *
4
- * `new Codex({ config, env })` -> `thread.run(prompt)` -> `Turn` whose
5
- * `finalResponse` is the completed agent output. CLI fallback: `codex exec`.
6
- */
7
1
  import { type LaneAdapter } from "./types.js";
8
2
  export interface CodexAdapterDeps {
9
3
  sdkLoader?: () => Promise<unknown>;
@@ -1,3 +1,4 @@
1
+ import type { CodexExecutionMode } from "../graph.js";
1
2
  export declare const LANE_KINDS: readonly ["claude", "codex", "cursor", "grok"];
2
3
  export type LaneKind = (typeof LANE_KINDS)[number];
3
4
  export interface LaneJob {
@@ -7,6 +8,8 @@ export interface LaneJob {
7
8
  /** Shell command alternative (handled by the daemon's built-in executor). */
8
9
  command?: string;
9
10
  cwd?: string;
11
+ /** Repository checks remain native defaults unless read-only is explicitly selected. */
12
+ codexMode?: CodexExecutionMode;
10
13
  /** Environment for the lane process — never credentials from the vault. */
11
14
  env?: Record<string, string>;
12
15
  /** Per-invocation budget in ms. Default 120000. */
@@ -19,6 +22,8 @@ export interface LaneResult {
19
22
  output: string;
20
23
  durationMs: number;
21
24
  error?: string;
25
+ /** False when retry could overlap a timed-out execution or a policy refusal. */
26
+ retryable?: boolean;
22
27
  }
23
28
  /**
24
29
  * A lane maturity probe: is the substrate actually wired on THIS machine
@@ -52,8 +57,15 @@ export declare class LaneAdapterShapeError extends Error {
52
57
  export declare const DEFAULT_LANE_TIMEOUT_MS = 120000;
53
58
  /** Is a binary present on PATH (read-only existence check; never runs it)? */
54
59
  export declare function binaryOnPath(binary: string): boolean;
55
- /** Run a promise with a bounded wall-clock budget. */
56
- export declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, what: string): Promise<T>;
60
+ /** Preserve native subscription configuration while excluding API billing and server secrets. */
61
+ export declare function subscriptionEnvironment(overrides?: Record<string, string>): Record<string, string>;
62
+ /** SDKs that merge process.env need explicit undefined entries for excluded fields. */
63
+ export declare function subscriptionSdkEnvironment(overrides?: Record<string, string>): Record<string, string | undefined>;
64
+ export declare class LaneTimeoutError extends Error {
65
+ constructor(what: string, timeoutMs: number);
66
+ }
67
+ /** A timeout requests cancellation. Callers mark it non-retryable. */
68
+ export declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, what: string, cancel?: () => void): Promise<T>;
57
69
  /** Spawn a CLI binary with a bounded timeout; maps ENOENT to a missing-substrate error. */
58
70
  export declare function spawnCli(cmd: string[], opts: {
59
71
  cwd?: string;
@@ -61,4 +73,5 @@ export declare function spawnCli(cmd: string[], opts: {
61
73
  timeoutMs?: number;
62
74
  binaryName: string;
63
75
  packageHint: string;
64
- }): LaneResult;
76
+ stdoutOnly?: boolean;
77
+ }): Promise<LaneResult>;
@@ -0,0 +1,7 @@
1
+ import type { OpenApiDocument } from "@hasna/contracts";
2
+ /** One API description drives served discovery and generated SDK operations. */
3
+ export declare function buildOpenApi(version: string): OpenApiDocument & {
4
+ servers: {
5
+ url: string;
6
+ }[];
7
+ };
package/types/paths.d.ts CHANGED
@@ -15,6 +15,15 @@
15
15
  * data-kind override `HASNA_DATA_HOME`. An existing local store never becomes
16
16
  * invisible on upgrade.
17
17
  */
18
+ export type PathKind = "config" | "data" | "state" | "cache";
19
+ export interface PathsResolverOptions {
20
+ app: string;
21
+ internal?: boolean;
22
+ platform?: string;
23
+ home?: string;
24
+ env?: Record<string, string | undefined>;
25
+ }
26
+ export declare function dataDir(options: PathsResolverOptions): string;
18
27
  /** The workflows app slug used by the @hasna/paths resolver. */
19
28
  export declare const APP: "workflows";
20
29
  /** The store database file name, the marker that a store lives at a data home. */
@@ -33,7 +42,7 @@ export declare function getLegacyDataDir(env?: NodeJS.ProcessEnv): string;
33
42
  * pre-existing `$HOME`-first resolution so the resolver follows the same home
34
43
  * the legacy path does.
35
44
  */
36
- export declare function getResolverDataDir(env?: NodeJS.ProcessEnv): string;
45
+ export declare function getResolverDataDir(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): string;
37
46
  /**
38
47
  * Whether the resolver (XDG) data home should be adopted as the effective
39
48
  * workflows data home. The resolver home is adopted only when the operator