@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.
- package/dist/control-plane-client.d.ts +47 -2
- package/dist/control-plane-client.js +142 -0
- package/dist/d1-store.d.ts +19 -2
- package/dist/d1-store.js +110 -0
- package/dist/graph/context-query-contracts.d.ts +1 -0
- package/dist/graph/context-query-contracts.js +5 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/market-client.d.ts +7 -0
- package/dist/market-client.js +22 -0
- package/dist/model-registry.js +74 -0
- package/dist/operations/providers/default.js +6 -0
- package/dist/operations/services/deploy.js +8 -8
- package/dist/operations-registry.js +1 -0
- package/dist/scripts/tenant-d1-migrate-local.js +1 -0
- package/dist/sdk-types.d.ts +30 -3
- package/dist/sdk-types.js +5 -1
- package/dist/sdk.d.ts +6 -1
- package/dist/sdk.js +20 -0
- package/dist/seeds/errors.d.ts +5 -0
- package/dist/seeds/errors.js +22 -0
- package/dist/seeds/index.d.ts +20 -0
- package/dist/seeds/index.js +53 -0
- package/dist/seeds/loader.d.ts +8 -0
- package/dist/seeds/loader.js +32 -0
- package/dist/seeds/normalize.d.ts +13 -0
- package/dist/seeds/normalize.js +275 -0
- package/dist/seeds/planner.d.ts +10 -0
- package/dist/seeds/planner.js +111 -0
- package/dist/seeds/schema.d.ts +2 -0
- package/dist/seeds/schema.js +544 -0
- package/dist/seeds/types.d.ts +220 -0
- package/dist/seeds/types.js +4 -0
- package/dist/stores/operational-store.d.ts +11 -1
- package/dist/stores/operational-store.js +244 -0
- package/package.json +5 -1
|
@@ -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
|
+
};
|
|
@@ -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.
|
|
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",
|