@treeseed/sdk 0.8.12 → 0.8.14

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.
@@ -0,0 +1,220 @@
1
+ export declare const SEED_ENVIRONMENTS: readonly ["local", "staging", "prod"];
2
+ export type SeedEnvironment = typeof SEED_ENVIRONMENTS[number];
3
+ export type SeedDiagnosticSeverity = 'error' | 'warning';
4
+ export type SeedPlanActionType = 'create' | 'update' | 'unchanged' | 'skip' | 'delete' | 'error';
5
+ export type SeedResourceKind = 'team' | 'repositoryHost' | 'project' | 'hubRepository' | 'capacityProvider' | 'capacityLane' | 'capacityGrant' | 'workPolicy' | 'product' | 'catalogArtifact';
6
+ export type SeedDiagnostic = {
7
+ severity: SeedDiagnosticSeverity;
8
+ code: string;
9
+ message: string;
10
+ path?: string;
11
+ };
12
+ export type SeedResourceBase = {
13
+ key: string;
14
+ environments?: SeedEnvironment[];
15
+ };
16
+ export type SeedManifest = {
17
+ name: string;
18
+ version: 1;
19
+ description?: string;
20
+ defaultEnvironments?: SeedEnvironment[];
21
+ environments: SeedEnvironment[];
22
+ resources: SeedManifestResources;
23
+ };
24
+ export type SeedManifestResources = {
25
+ teams: SeedTeamResource[];
26
+ repositoryHosts: SeedRepositoryHostResource[];
27
+ projects: SeedProjectResource[];
28
+ hubRepositories: SeedHubRepositoryResource[];
29
+ products: SeedProductResource[];
30
+ catalogArtifacts: SeedCatalogArtifactResource[];
31
+ capacityProviders: SeedCapacityProviderResource[];
32
+ capacityGrants: SeedCapacityGrantResource[];
33
+ workPolicies: SeedWorkPolicyResource[];
34
+ agentPools: Record<string, unknown>[];
35
+ };
36
+ export type SeedTeamResource = SeedResourceBase & {
37
+ slug: string;
38
+ name?: string;
39
+ displayName?: string;
40
+ logoUrl?: string;
41
+ profileSummary?: string;
42
+ metadata?: Record<string, unknown>;
43
+ };
44
+ export type SeedProjectRepository = {
45
+ role: string;
46
+ provider: string;
47
+ owner: string;
48
+ name: string;
49
+ gitUrl: string;
50
+ defaultBranch?: string;
51
+ checkoutPath?: string;
52
+ submodulePath?: string;
53
+ webUrl?: string;
54
+ };
55
+ export type SeedProjectResource = SeedResourceBase & {
56
+ team: string;
57
+ slug: string;
58
+ name: string;
59
+ description?: string;
60
+ kind?: string;
61
+ repository: SeedProjectRepository;
62
+ metadata?: Record<string, unknown>;
63
+ };
64
+ export type SeedRepositoryHostResource = SeedResourceBase & {
65
+ team: string;
66
+ provider: string;
67
+ name: string;
68
+ ownership?: string;
69
+ accountLabel?: string;
70
+ organizationOrOwner: string;
71
+ defaultVisibility?: string;
72
+ softwareRepositoryNameTemplate?: string;
73
+ contentRepositoryNameTemplate?: string;
74
+ branchPolicy?: Record<string, unknown>;
75
+ workflowPolicy?: Record<string, unknown>;
76
+ allowedProjectKinds?: string[];
77
+ status?: string;
78
+ credentialRef?: string;
79
+ metadata?: Record<string, unknown>;
80
+ };
81
+ export type SeedHubRepositoryResource = SeedResourceBase & {
82
+ project: string;
83
+ role: string;
84
+ repositoryHost?: string;
85
+ provider: string;
86
+ owner: string;
87
+ name: string;
88
+ gitUrl: string;
89
+ defaultBranch?: string;
90
+ currentBranch?: string;
91
+ submodulePath?: string;
92
+ status?: string;
93
+ accessPolicy?: Record<string, unknown>;
94
+ releasePolicy?: Record<string, unknown>;
95
+ publishPolicy?: Record<string, unknown>;
96
+ metadata?: Record<string, unknown>;
97
+ };
98
+ export type SeedProductResource = SeedResourceBase & {
99
+ team: string;
100
+ kind: string;
101
+ slug: string;
102
+ title: string;
103
+ summary?: string;
104
+ visibility?: string;
105
+ listingEnabled?: boolean;
106
+ offerMode?: string;
107
+ manifestKey?: string;
108
+ artifactKey?: string;
109
+ searchText?: string;
110
+ metadata?: Record<string, unknown>;
111
+ };
112
+ export type SeedCatalogArtifactResource = SeedResourceBase & {
113
+ product: string;
114
+ version: string;
115
+ kind: string;
116
+ contentKey: string;
117
+ manifestKey?: string;
118
+ publishedAt?: string;
119
+ metadata?: Record<string, unknown>;
120
+ };
121
+ export type SeedCapacityLaneResource = SeedResourceBase & {
122
+ name: string;
123
+ businessModel?: string;
124
+ modelFamily?: string;
125
+ modelClass?: string;
126
+ regionPolicy?: string;
127
+ unit?: string;
128
+ scarcityLevel?: string;
129
+ hardLimits?: Record<string, unknown>;
130
+ routingPolicy?: Record<string, unknown>;
131
+ metadata?: Record<string, unknown>;
132
+ };
133
+ export type SeedCapacityProviderRegistrationApiKey = {
134
+ createIfMissing?: boolean;
135
+ name?: string;
136
+ scopes?: string[];
137
+ expiresAt?: string;
138
+ };
139
+ export type SeedCapacityProviderRegistration = {
140
+ apiKey?: SeedCapacityProviderRegistrationApiKey;
141
+ };
142
+ export type SeedCapacityProviderResource = SeedResourceBase & {
143
+ team: string;
144
+ name: string;
145
+ kind?: string;
146
+ provider: string;
147
+ billingScope?: string;
148
+ monthlyCreditBudget?: number;
149
+ dailyCreditBudget?: number;
150
+ maxConcurrentWorkdays?: number;
151
+ maxConcurrentWorkers?: number;
152
+ capacityModel?: Record<string, unknown>;
153
+ registration?: SeedCapacityProviderRegistration;
154
+ metadata?: Record<string, unknown>;
155
+ lanes?: SeedCapacityLaneResource[];
156
+ };
157
+ export type SeedCapacityGrantResource = SeedResourceBase & {
158
+ provider: string;
159
+ lane?: string;
160
+ team: string;
161
+ project?: string;
162
+ environment?: SeedEnvironment;
163
+ grantScope?: string;
164
+ dailyCreditLimit?: number;
165
+ weeklyCreditLimit?: number;
166
+ monthlyCreditLimit?: number;
167
+ dailyUsdLimit?: number;
168
+ weeklyQuotaMinutes?: number;
169
+ monthlyProviderUnits?: number;
170
+ priorityWeight?: number;
171
+ overflowPolicy?: string;
172
+ state?: string;
173
+ metadata?: Record<string, unknown>;
174
+ };
175
+ export type SeedWorkPolicyResource = SeedResourceBase & {
176
+ project: string;
177
+ environment: SeedEnvironment;
178
+ enabled?: boolean;
179
+ startCron?: string;
180
+ durationMinutes?: number;
181
+ maxRunners?: number;
182
+ maxWorkersPerRunner?: number;
183
+ dailyCreditBudget?: number;
184
+ maxQueuedTasks?: number;
185
+ maxQueuedCredits?: number;
186
+ autoscale?: Record<string, unknown>;
187
+ creditWeights?: unknown[];
188
+ metadata?: Record<string, unknown>;
189
+ };
190
+ export type NormalizedSeedResource = {
191
+ kind: SeedResourceKind;
192
+ key: string;
193
+ label: string;
194
+ environments: SeedEnvironment[];
195
+ payload: Record<string, unknown>;
196
+ parentKey?: string;
197
+ };
198
+ export type SeedPlanAction = NormalizedSeedResource & {
199
+ action: SeedPlanActionType;
200
+ reason?: string;
201
+ existing?: Record<string, unknown> | null;
202
+ };
203
+ export type SeedPlanSummary = Record<SeedPlanActionType, number>;
204
+ export type SeedCurrentResource = {
205
+ key: string;
206
+ kind: SeedResourceKind;
207
+ payload: Record<string, unknown>;
208
+ existing?: Record<string, unknown> | null;
209
+ };
210
+ export type SeedPlan = {
211
+ ok: boolean;
212
+ seed: string;
213
+ version: 1;
214
+ mode: 'plan' | 'validate' | 'apply';
215
+ environments: SeedEnvironment[];
216
+ summary: SeedPlanSummary;
217
+ actions: SeedPlanAction[];
218
+ diagnostics: SeedDiagnostic[];
219
+ manifestPath: string;
220
+ };
@@ -0,0 +1,4 @@
1
+ const SEED_ENVIRONMENTS = ["local", "staging", "prod"];
2
+ export {
3
+ SEED_ENVIRONMENTS
4
+ };
@@ -1,6 +1,11 @@
1
- import type { PrioritySnapshot, SdkCreatePrioritySnapshotRequest, SdkAppendTaskEventRequest, SdkClaimTaskRequest, SdkCloseWorkDayRequest, SdkCompleteTaskRequest, SdkCreateReportRequest, SdkCreateTaskRequest, SdkFailTaskRequest, SdkGraphRunEntity, SdkClaimWorkdayManagerLeaseRequest, SdkCreateWorkdayRequest, SdkPriorityOverrideRequest, SdkRecordRepositoryClaimRequest, SdkRecordRunnerScaleDecisionRequest, SdkReportEntity, SdkRecordScaleDecisionRequest, SdkRecordWorkerRunnerRequest, SdkRecordTaskCreditsRequest, SdkReleaseWorkdayManagerLeaseRequest, SdkStartWorkDayRequest, SdkTaskEntity, SdkTaskEventEntity, SdkTaskOutputEntity, SdkTaskProgressRequest, SdkTaskSearchRequest, SdkUpsertWorkPolicyRequest, SdkUpdateWorkDayGraphRequest, SdkWorkDayEntity, RepositoryClaim, RunnerScaleDecision, ScaleDecision, TaskCreditLedgerEntry, WorkdayPolicy, WorkdayManagerLease, WorkdayRequest, WorkerRunner } from '../sdk-types.ts';
1
+ import type { ApprovalRequest, CreateApprovalRequestRequest, DecideApprovalRequestRequest, PrioritySnapshot, ListApprovalRequestsRequest, SdkCreatePrioritySnapshotRequest, SdkAppendTaskEventRequest, SdkClaimTaskRequest, SdkCloseWorkDayRequest, SdkCompleteTaskRequest, SdkCreateReportRequest, SdkCreateTaskRequest, SdkFailTaskRequest, SdkGraphRunEntity, SdkClaimWorkdayManagerLeaseRequest, SdkCreateWorkdayRequest, SdkPriorityOverrideRequest, SdkRecordRepositoryClaimRequest, SdkRecordRunnerScaleDecisionRequest, SdkReportEntity, SdkRecordScaleDecisionRequest, SdkRecordWorkerRunnerRequest, SdkRecordTaskCreditsRequest, SdkReleaseWorkdayManagerLeaseRequest, SdkStartWorkDayRequest, SdkTaskEntity, SdkTaskEventEntity, SdkTaskOutputEntity, SdkTaskProgressRequest, SdkTaskSearchRequest, SdkUpsertWorkPolicyRequest, SdkUpdateWorkDayGraphRequest, SdkWorkDayEntity, RepositoryClaim, RunnerScaleDecision, ScaleDecision, TaskCreditLedgerEntry, UpsertTeamInboxItemRequest, WorkdayPolicy, WorkdayManagerLease, WorkdayRequest, WorkerRunner } from '../sdk-types.ts';
2
+ import type { InboxItem } from '../project-workflow.ts';
2
3
  import { SqliteStoreBase, type DatabaseRow } from './helpers.ts';
3
4
  export declare class OperationalStore extends SqliteStoreBase {
5
+ private governanceInitialized;
6
+ private runnerInitialized;
7
+ private ensureGovernanceSchema;
8
+ private ensureRunnerSchema;
4
9
  getWorkDay(id: string): Promise<SdkWorkDayEntity | null>;
5
10
  searchWorkDays(limit?: number): Promise<SdkWorkDayEntity[]>;
6
11
  startWorkDay(request: SdkStartWorkDayRequest): Promise<SdkWorkDayEntity | null>;
@@ -27,6 +32,7 @@ export declare class OperationalStore extends SqliteStoreBase {
27
32
  listWorkdayRequests(projectId: string, environment: string, state?: string | null): Promise<WorkdayRequest[]>;
28
33
  claimWorkdayManagerLease(request: SdkClaimWorkdayManagerLeaseRequest): Promise<WorkdayManagerLease | null>;
29
34
  releaseWorkdayManagerLease(request: SdkReleaseWorkdayManagerLeaseRequest): Promise<WorkdayManagerLease | null>;
35
+ listWorkdayManagerLeases(projectId: string, environment: string): Promise<WorkdayManagerLease[]>;
30
36
  recordWorkerRunner(request: SdkRecordWorkerRunnerRequest): Promise<WorkerRunner | null>;
31
37
  listWorkerRunners(projectId: string, environment: string): Promise<WorkerRunner[]>;
32
38
  recordRepositoryClaim(request: SdkRecordRepositoryClaimRequest): Promise<RepositoryClaim | null>;
@@ -52,4 +58,8 @@ export declare class OperationalStore extends SqliteStoreBase {
52
58
  listTaskCredits(workDayId: string): Promise<TaskCreditLedgerEntry[]>;
53
59
  recordScaleDecision(request: SdkRecordScaleDecisionRequest): Promise<ScaleDecision | null>;
54
60
  getLatestScaleDecision(projectId: string, environment: string, poolName: string): Promise<ScaleDecision | null>;
61
+ createApprovalRequest(request: CreateApprovalRequestRequest): Promise<ApprovalRequest | null>;
62
+ listApprovalRequests(request?: ListApprovalRequestsRequest): Promise<ApprovalRequest[]>;
63
+ decideApprovalRequest(id: string, request: DecideApprovalRequestRequest): Promise<ApprovalRequest | null>;
64
+ upsertTeamInboxItem(request: UpsertTeamInboxItemRequest): Promise<InboxItem | null>;
55
65
  }
@@ -250,7 +250,135 @@ function scaleDecisionFromRow(row) {
250
250
  createdAt: String(row.created_at ?? nowIso())
251
251
  };
252
252
  }
253
+ function approvalRequestFromRow(row) {
254
+ return {
255
+ id: String(row.id ?? ""),
256
+ teamId: String(row.team_id ?? ""),
257
+ projectId: String(row.project_id ?? ""),
258
+ workDayId: row.work_day_id === void 0 || row.work_day_id === null ? null : String(row.work_day_id),
259
+ taskId: row.task_id === void 0 || row.task_id === null ? null : String(row.task_id),
260
+ kind: String(row.kind ?? ""),
261
+ state: String(row.state ?? "pending"),
262
+ severity: String(row.severity ?? "medium"),
263
+ requestedByType: String(row.requested_by_type ?? "worker"),
264
+ requestedById: row.requested_by_id === void 0 || row.requested_by_id === null ? null : String(row.requested_by_id),
265
+ title: String(row.title ?? ""),
266
+ summary: String(row.summary ?? ""),
267
+ options: parseJsonValue(row.options_json, []),
268
+ recommendation: parseJsonValue(row.recommendation_json, {}),
269
+ policySnapshot: parseJsonValue(row.policy_snapshot_json, {}),
270
+ expiresAt: row.expires_at === void 0 || row.expires_at === null ? null : String(row.expires_at),
271
+ decidedByType: row.decided_by_type === void 0 || row.decided_by_type === null ? null : String(row.decided_by_type),
272
+ decidedById: row.decided_by_id === void 0 || row.decided_by_id === null ? null : String(row.decided_by_id),
273
+ decidedAt: row.decided_at === void 0 || row.decided_at === null ? null : String(row.decided_at),
274
+ decision: row.decision_json === void 0 || row.decision_json === null ? null : parseJsonValue(row.decision_json, null),
275
+ metadata: parseJsonValue(row.metadata_json, {}),
276
+ createdAt: String(row.created_at ?? nowIso()),
277
+ updatedAt: String(row.updated_at ?? nowIso())
278
+ };
279
+ }
280
+ function teamInboxItemFromRow(row) {
281
+ return {
282
+ id: String(row.id ?? ""),
283
+ teamId: String(row.team_id ?? ""),
284
+ projectId: row.project_id === void 0 || row.project_id === null ? null : String(row.project_id),
285
+ kind: String(row.kind ?? ""),
286
+ state: String(row.state ?? "informational"),
287
+ title: String(row.title ?? ""),
288
+ summary: row.summary === void 0 || row.summary === null ? null : String(row.summary),
289
+ href: row.href === void 0 || row.href === null ? null : String(row.href),
290
+ itemKey: row.item_key === void 0 || row.item_key === null ? null : String(row.item_key),
291
+ metadata: parseJsonValue(row.metadata_json, {}),
292
+ createdAt: String(row.created_at ?? nowIso()),
293
+ updatedAt: String(row.updated_at ?? nowIso())
294
+ };
295
+ }
253
296
  class OperationalStore extends SqliteStoreBase {
297
+ governanceInitialized = false;
298
+ runnerInitialized = false;
299
+ async ensureGovernanceSchema() {
300
+ if (this.governanceInitialized) return;
301
+ await this.execute(`CREATE TABLE IF NOT EXISTS approval_requests (
302
+ id TEXT PRIMARY KEY,
303
+ team_id TEXT NOT NULL,
304
+ project_id TEXT NOT NULL,
305
+ work_day_id TEXT,
306
+ task_id TEXT,
307
+ kind TEXT NOT NULL,
308
+ state TEXT NOT NULL DEFAULT 'pending',
309
+ severity TEXT NOT NULL DEFAULT 'medium',
310
+ requested_by_type TEXT NOT NULL DEFAULT 'worker',
311
+ requested_by_id TEXT,
312
+ title TEXT NOT NULL,
313
+ summary TEXT NOT NULL,
314
+ options_json TEXT NOT NULL DEFAULT '[]',
315
+ recommendation_json TEXT NOT NULL DEFAULT '{}',
316
+ policy_snapshot_json TEXT NOT NULL DEFAULT '{}',
317
+ expires_at TEXT,
318
+ decided_by_type TEXT,
319
+ decided_by_id TEXT,
320
+ decided_at TEXT,
321
+ decision_json TEXT,
322
+ metadata_json TEXT NOT NULL DEFAULT '{}',
323
+ created_at TEXT NOT NULL,
324
+ updated_at TEXT NOT NULL
325
+ )`);
326
+ await this.execute("CREATE INDEX IF NOT EXISTS idx_approval_requests_team_state ON approval_requests(team_id, state, created_at DESC)");
327
+ await this.execute("CREATE INDEX IF NOT EXISTS idx_approval_requests_project_workday ON approval_requests(project_id, work_day_id, state, created_at DESC)");
328
+ await this.execute(`CREATE TABLE IF NOT EXISTS team_inbox_items (
329
+ id TEXT PRIMARY KEY,
330
+ team_id TEXT NOT NULL,
331
+ project_id TEXT,
332
+ kind TEXT NOT NULL,
333
+ state TEXT NOT NULL,
334
+ title TEXT NOT NULL,
335
+ summary TEXT,
336
+ href TEXT,
337
+ item_key TEXT,
338
+ metadata_json TEXT NOT NULL DEFAULT '{}',
339
+ created_at TEXT NOT NULL,
340
+ updated_at TEXT NOT NULL
341
+ )`);
342
+ await this.execute("CREATE INDEX IF NOT EXISTS idx_team_inbox_items_team_created ON team_inbox_items(team_id, created_at DESC)");
343
+ this.governanceInitialized = true;
344
+ }
345
+ async ensureRunnerSchema() {
346
+ if (this.runnerInitialized) return;
347
+ await this.execute(`CREATE TABLE IF NOT EXISTS workday_manager_leases (
348
+ id TEXT PRIMARY KEY,
349
+ project_id TEXT NOT NULL,
350
+ environment TEXT NOT NULL,
351
+ work_day_id TEXT,
352
+ manager_id TEXT NOT NULL,
353
+ state TEXT NOT NULL DEFAULT 'active',
354
+ heartbeat_at TEXT NOT NULL,
355
+ expires_at TEXT NOT NULL,
356
+ metadata_json TEXT NOT NULL DEFAULT '{}',
357
+ created_at TEXT NOT NULL,
358
+ updated_at TEXT NOT NULL
359
+ )`);
360
+ await this.execute("CREATE INDEX IF NOT EXISTS idx_workday_manager_leases_active ON workday_manager_leases(project_id, environment, state, heartbeat_at DESC)");
361
+ await this.execute(`CREATE TABLE IF NOT EXISTS worker_runners (
362
+ id TEXT PRIMARY KEY,
363
+ project_id TEXT NOT NULL,
364
+ environment TEXT NOT NULL,
365
+ runner_id TEXT NOT NULL,
366
+ runner_service_name TEXT NOT NULL,
367
+ volume_identity TEXT NOT NULL,
368
+ state TEXT NOT NULL,
369
+ max_local_workers INTEGER NOT NULL DEFAULT 4,
370
+ active_local_workers INTEGER NOT NULL DEFAULT 0,
371
+ available_capacity INTEGER NOT NULL DEFAULT 0,
372
+ last_heartbeat_at TEXT NOT NULL,
373
+ claimed_repository_ids_json TEXT NOT NULL DEFAULT '[]',
374
+ metadata_json TEXT NOT NULL DEFAULT '{}',
375
+ created_at TEXT NOT NULL,
376
+ updated_at TEXT NOT NULL
377
+ )`);
378
+ await this.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_worker_runners_identity ON worker_runners(project_id, environment, runner_id)");
379
+ await this.execute("CREATE INDEX IF NOT EXISTS idx_worker_runners_state_capacity ON worker_runners(project_id, environment, state, available_capacity DESC)");
380
+ this.runnerInitialized = true;
381
+ }
254
382
  async getWorkDay(id) {
255
383
  const row = await this.selectFirst(`SELECT * FROM work_days WHERE id = ${toSqlValue(id)} LIMIT 1`);
256
384
  return row ? workDayFromRow(row) : null;
@@ -505,6 +633,7 @@ class OperationalStore extends SqliteStoreBase {
505
633
  return rows.map(workdayRequestFromRow);
506
634
  }
507
635
  async claimWorkdayManagerLease(request) {
636
+ await this.ensureRunnerSchema();
508
637
  const timestamp = request.now ?? nowIso();
509
638
  const active = await this.selectFirst(
510
639
  `SELECT * FROM workday_manager_leases WHERE project_id = ${toSqlValue(request.projectId)} AND environment = ${toSqlValue(request.environment)} AND state = 'active' ORDER BY updated_at DESC LIMIT 1`
@@ -540,6 +669,7 @@ class OperationalStore extends SqliteStoreBase {
540
669
  return row ? workdayManagerLeaseFromRow(row) : null;
541
670
  }
542
671
  async releaseWorkdayManagerLease(request) {
672
+ await this.ensureRunnerSchema();
543
673
  const timestamp = nowIso();
544
674
  await this.execute(
545
675
  `UPDATE workday_manager_leases SET state = 'released', updated_at = ${toSqlValue(timestamp)} WHERE id = ${toSqlValue(request.id)} AND manager_id = ${toSqlValue(request.managerId)}`
@@ -547,7 +677,16 @@ class OperationalStore extends SqliteStoreBase {
547
677
  const row = await this.selectFirst(`SELECT * FROM workday_manager_leases WHERE id = ${toSqlValue(request.id)} LIMIT 1`);
548
678
  return row ? workdayManagerLeaseFromRow(row) : null;
549
679
  }
680
+ async listWorkdayManagerLeases(projectId, environment) {
681
+ await this.ensureRunnerSchema();
682
+ if (!await this.tableExists("workday_manager_leases")) return [];
683
+ const rows = await this.selectAll(
684
+ `SELECT * FROM workday_manager_leases WHERE project_id = ${toSqlValue(projectId)} AND environment = ${toSqlValue(environment)} ORDER BY heartbeat_at DESC, updated_at DESC LIMIT 10`
685
+ );
686
+ return rows.map(workdayManagerLeaseFromRow);
687
+ }
550
688
  async recordWorkerRunner(request) {
689
+ await this.ensureRunnerSchema();
551
690
  const timestamp = nowIso();
552
691
  const id = request.id ?? `${request.projectId}:${request.environment}:${request.runnerId}`;
553
692
  const maxLocalWorkers = Number(request.maxLocalWorkers ?? 4);
@@ -577,6 +716,7 @@ class OperationalStore extends SqliteStoreBase {
577
716
  return row ? workerRunnerFromRow(row) : null;
578
717
  }
579
718
  async listWorkerRunners(projectId, environment) {
719
+ await this.ensureRunnerSchema();
580
720
  if (!await this.tableExists("worker_runners")) return [];
581
721
  const rows = await this.selectAll(
582
722
  `SELECT * FROM worker_runners WHERE project_id = ${toSqlValue(projectId)} AND environment = ${toSqlValue(environment)} ORDER BY runner_id ASC`
@@ -786,6 +926,110 @@ class OperationalStore extends SqliteStoreBase {
786
926
  );
787
927
  return row ? scaleDecisionFromRow(row) : null;
788
928
  }
929
+ async createApprovalRequest(request) {
930
+ await this.ensureGovernanceSchema();
931
+ const id = request.id ?? crypto.randomUUID();
932
+ const existing = await this.selectFirst(`SELECT * FROM approval_requests WHERE id = ${toSqlValue(id)} LIMIT 1`);
933
+ if (existing && String(existing.state ?? "pending") !== "pending") {
934
+ return approvalRequestFromRow(existing);
935
+ }
936
+ const timestamp = nowIso();
937
+ await this.execute(
938
+ `INSERT OR REPLACE INTO approval_requests (
939
+ id, team_id, project_id, work_day_id, task_id, kind, state, severity, requested_by_type,
940
+ requested_by_id, title, summary, options_json, recommendation_json, policy_snapshot_json,
941
+ expires_at, decided_by_type, decided_by_id, decided_at, decision_json, metadata_json, created_at, updated_at
942
+ ) VALUES (
943
+ ${toSqlValue(id)},
944
+ ${toSqlValue(request.teamId)},
945
+ ${toSqlValue(request.projectId)},
946
+ ${toSqlValue(request.workDayId ?? null)},
947
+ ${toSqlValue(request.taskId ?? null)},
948
+ ${toSqlValue(request.kind)},
949
+ ${toSqlValue(existing?.state ?? "pending")},
950
+ ${toSqlValue(request.severity ?? existing?.severity ?? "medium")},
951
+ ${toSqlValue(request.requestedByType ?? existing?.requested_by_type ?? "worker")},
952
+ ${toSqlValue(request.requestedById ?? existing?.requested_by_id ?? null)},
953
+ ${toSqlValue(request.title)},
954
+ ${toSqlValue(request.summary)},
955
+ ${toSqlValue(json(request.options ?? parseJsonValue(existing?.options_json, [])))},
956
+ ${toSqlValue(json(request.recommendation ?? parseJsonValue(existing?.recommendation_json, {})))},
957
+ ${toSqlValue(json(request.policySnapshot ?? parseJsonValue(existing?.policy_snapshot_json, {})))},
958
+ ${toSqlValue(request.expiresAt ?? existing?.expires_at ?? null)},
959
+ ${toSqlValue(existing?.decided_by_type ?? null)},
960
+ ${toSqlValue(existing?.decided_by_id ?? null)},
961
+ ${toSqlValue(existing?.decided_at ?? null)},
962
+ ${toSqlValue(existing?.decision_json ?? null)},
963
+ ${toSqlValue(json(request.metadata ?? parseJsonValue(existing?.metadata_json, {})))},
964
+ COALESCE((SELECT created_at FROM approval_requests WHERE id = ${toSqlValue(id)}), ${toSqlValue(timestamp)}),
965
+ ${toSqlValue(timestamp)}
966
+ )`
967
+ );
968
+ const row = await this.selectFirst(`SELECT * FROM approval_requests WHERE id = ${toSqlValue(id)} LIMIT 1`);
969
+ return row ? approvalRequestFromRow(row) : null;
970
+ }
971
+ async listApprovalRequests(request = {}) {
972
+ await this.ensureGovernanceSchema();
973
+ const clauses = [];
974
+ if (request.projectId) clauses.push(`project_id = ${toSqlValue(request.projectId)}`);
975
+ if (request.teamId) clauses.push(`team_id = ${toSqlValue(request.teamId)}`);
976
+ if (request.state) {
977
+ const states = Array.isArray(request.state) ? request.state : [request.state];
978
+ clauses.push(`state IN (${states.map((entry) => toSqlValue(String(entry))).join(", ")})`);
979
+ }
980
+ const rows = await this.selectAll(
981
+ `SELECT * FROM approval_requests ${clauses.length ? `WHERE ${clauses.join(" AND ")}` : ""} ORDER BY created_at DESC LIMIT ${Math.max(1, Math.min(500, Number(request.limit ?? 100)))}`
982
+ );
983
+ return rows.map(approvalRequestFromRow);
984
+ }
985
+ async decideApprovalRequest(id, request) {
986
+ await this.ensureGovernanceSchema();
987
+ const existing = await this.selectFirst(`SELECT * FROM approval_requests WHERE id = ${toSqlValue(id)} LIMIT 1`);
988
+ if (!existing) return null;
989
+ const timestamp = nowIso();
990
+ const decision = {
991
+ ...request.decision ?? {},
992
+ ...request.optionId ? { optionId: request.optionId } : {},
993
+ ...request.note ? { note: request.note } : {}
994
+ };
995
+ await this.execute(
996
+ `UPDATE approval_requests
997
+ SET state = ${toSqlValue(String(request.state || "pending"))},
998
+ decided_by_type = ${toSqlValue(request.decidedByType ?? "user")},
999
+ decided_by_id = ${toSqlValue(request.decidedById ?? null)},
1000
+ decided_at = ${toSqlValue(timestamp)},
1001
+ decision_json = ${toSqlValue(json(decision))},
1002
+ updated_at = ${toSqlValue(timestamp)}
1003
+ WHERE id = ${toSqlValue(id)}`
1004
+ );
1005
+ const row = await this.selectFirst(`SELECT * FROM approval_requests WHERE id = ${toSqlValue(id)} LIMIT 1`);
1006
+ return row ? approvalRequestFromRow(row) : null;
1007
+ }
1008
+ async upsertTeamInboxItem(request) {
1009
+ await this.ensureGovernanceSchema();
1010
+ const id = request.id ?? request.itemKey ?? crypto.randomUUID();
1011
+ const timestamp = nowIso();
1012
+ await this.execute(
1013
+ `INSERT OR REPLACE INTO team_inbox_items (
1014
+ id, team_id, project_id, kind, state, title, summary, href, item_key, metadata_json, created_at, updated_at
1015
+ ) VALUES (
1016
+ ${toSqlValue(id)},
1017
+ ${toSqlValue(request.teamId)},
1018
+ ${toSqlValue(request.projectId ?? null)},
1019
+ ${toSqlValue(request.kind)},
1020
+ ${toSqlValue(request.state)},
1021
+ ${toSqlValue(request.title)},
1022
+ ${toSqlValue(request.summary ?? null)},
1023
+ ${toSqlValue(request.href ?? null)},
1024
+ ${toSqlValue(request.itemKey ?? null)},
1025
+ ${toSqlValue(json(request.metadata ?? {}))},
1026
+ COALESCE((SELECT created_at FROM team_inbox_items WHERE id = ${toSqlValue(id)}), ${toSqlValue(timestamp)}),
1027
+ ${toSqlValue(timestamp)}
1028
+ )`
1029
+ );
1030
+ const row = await this.selectFirst(`SELECT * FROM team_inbox_items WHERE id = ${toSqlValue(id)} LIMIT 1`);
1031
+ return row ? teamInboxItemFromRow(row) : null;
1032
+ }
789
1033
  }
790
1034
  export {
791
1035
  OperationalStore
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/sdk",
3
- "version": "0.8.12",
3
+ "version": "0.8.14",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -112,6 +112,10 @@
112
112
  "types": "./dist/fixture-support.d.ts",
113
113
  "default": "./dist/fixture-support.js"
114
114
  },
115
+ "./seeds": {
116
+ "types": "./dist/seeds/index.d.ts",
117
+ "default": "./dist/seeds/index.js"
118
+ },
115
119
  "./scripts/verify-driver": "./scripts/verify-driver.mjs",
116
120
  "./workflow-support": {
117
121
  "types": "./dist/workflow-support.d.ts",