@owlmeans/viable-common 0.0.40 → 0.0.42
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/README.md +3 -3
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/agent-presentation/SKILL.md +1 -1
- package/agent-meta/skills/viable-common/SKILL.md +323 -401
- package/build/blueprint/consts.d.ts +35 -1
- package/build/blueprint/consts.d.ts.map +1 -1
- package/build/blueprint/consts.js +35 -0
- package/build/blueprint/consts.js.map +1 -1
- package/build/blueprint/types.d.ts +7 -0
- package/build/blueprint/types.d.ts.map +1 -1
- package/build/blueprint/types.js.map +1 -1
- package/build/branding.d.ts +3 -0
- package/build/branding.d.ts.map +1 -1
- package/build/branding.js +11 -1
- package/build/branding.js.map +1 -1
- package/build/connect/consts.d.ts +8 -0
- package/build/connect/consts.d.ts.map +1 -1
- package/build/connect/consts.js +8 -0
- package/build/connect/consts.js.map +1 -1
- package/build/connect/entrypoints.d.ts +15 -1
- package/build/connect/entrypoints.d.ts.map +1 -1
- package/build/connect/entrypoints.js +13 -1
- package/build/connect/entrypoints.js.map +1 -1
- package/build/connect/references.d.ts +14 -1
- package/build/connect/references.d.ts.map +1 -1
- package/build/connect/references.js +4 -0
- package/build/connect/references.js.map +1 -1
- package/build/connect/schemas.d.ts +7 -1
- package/build/connect/schemas.d.ts.map +1 -1
- package/build/connect/schemas.js +80 -0
- package/build/connect/schemas.js.map +1 -1
- package/build/connect/types.d.ts +51 -0
- package/build/connect/types.d.ts.map +1 -1
- package/build/planning/schemas.d.ts.map +1 -1
- package/build/planning/schemas.js +3 -0
- package/build/planning/schemas.js.map +1 -1
- package/build/planning/types.d.ts +5 -0
- package/build/planning/types.d.ts.map +1 -1
- package/build/slot/consts.d.ts +15 -1
- package/build/slot/consts.d.ts.map +1 -1
- package/build/slot/consts.js +19 -0
- package/build/slot/consts.js.map +1 -1
- package/build/slot/schemas.d.ts.map +1 -1
- package/build/slot/schemas.js +2 -1
- package/build/slot/schemas.js.map +1 -1
- package/build/slot/types.d.ts +44 -2
- package/build/slot/types.d.ts.map +1 -1
- package/build/types.d.ts +5 -0
- package/build/types.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/blueprint/consts.ts +36 -0
- package/src/blueprint/types.ts +7 -0
- package/src/branding.ts +11 -1
- package/src/connect/consts.ts +8 -0
- package/src/connect/entrypoints.ts +21 -2
- package/src/connect/references.ts +12 -1
- package/src/connect/schemas.ts +87 -1
- package/src/connect/types.ts +55 -0
- package/src/planning/schemas.ts +3 -0
- package/src/planning/types.ts +5 -0
- package/src/slot/consts.ts +20 -0
- package/src/slot/schemas.ts +2 -1
- package/src/slot/types.ts +31 -2
- package/src/types.ts +5 -0
- package/tests/branding.spec.ts +13 -2
- package/tests/connect-entrypoints.spec.ts +52 -5
- package/tests/planning.spec.ts +8 -0
|
@@ -3,7 +3,7 @@ import { route, RouteMethod } from '@owlmeans/route'
|
|
|
3
3
|
import { connect } from './consts.js'
|
|
4
4
|
import {
|
|
5
5
|
ConnectAttachBodySchema, ConnectConfirmBodySchema, ConnectConvertCreateBodySchema,
|
|
6
|
-
ConnectConvertProceedBodySchema, ConnectCreateBodySchema, ConnectInquiryParamsSchema,
|
|
6
|
+
ConnectConvertProceedBodySchema, ConnectCreateBodySchema, ConnectInquiryParamsSchema, ConnectKitApplyBodySchema,
|
|
7
7
|
ConnectModifyBodySchema, ConnectOpParamsSchema, ConnectOpResultSchema,
|
|
8
8
|
ConnectPipelineParamsSchema, ConnectPipelineResumeBodySchema, ConnectProjectBrandingSaveSchema,
|
|
9
9
|
ConnectProjectIdSchema, ConnectSessionOpenSchema, ConnectSessionParamsSchema, ConnectPullQuerySchema,
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
} from './schemas.js'
|
|
12
12
|
import type {
|
|
13
13
|
ConnectAttachBody, ConnectConfirmBody, ConnectConvertCreateBody, ConnectConvertProceedBody,
|
|
14
|
-
ConnectCreateBody, ConnectInquiryAnswerBody,
|
|
14
|
+
ConnectCreateBody, ConnectInquiryAnswerBody, ConnectKitApplyBody, ConnectKitApplyResult, ConnectKitDescribe,
|
|
15
|
+
ConnectModifyBody,
|
|
15
16
|
ConnectPipelineParams, ConnectPipelineResumeBody, ConnectProjectBranding,
|
|
16
17
|
ConnectProjectBrandingSave, ConnectSessionOpen,
|
|
17
18
|
ConnectPipelineState, ConnectProjectStatus, ConnectPullQuery, ConnectSessionParams,
|
|
@@ -143,6 +144,24 @@ export const connectProtocols = (opts: ConnectEntrypointOptions) => {
|
|
|
143
144
|
}),
|
|
144
145
|
contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema), body: typed<ConnectModifyBody>(ConnectModifyBodySchema) }, typed<ConnectProjectStatus>())
|
|
145
146
|
),
|
|
147
|
+
// Planning kits: one path, read and applied under the owned base like every project route.
|
|
148
|
+
kit: {
|
|
149
|
+
describe: protocol(
|
|
150
|
+
route(connect.project.kit.describe, '/project/:id/kits', {
|
|
151
|
+
parent: base, method: RouteMethod.GET,
|
|
152
|
+
}),
|
|
153
|
+
contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConnectKitDescribe>()),
|
|
154
|
+
),
|
|
155
|
+
apply: protocol(
|
|
156
|
+
route(connect.project.kit.apply, '/project/:id/kits', {
|
|
157
|
+
parent: base, method: RouteMethod.POST,
|
|
158
|
+
}),
|
|
159
|
+
contract.request({
|
|
160
|
+
params: typed<{ id: string }>(ConnectProjectIdSchema),
|
|
161
|
+
body: typed<ConnectKitApplyBody>(ConnectKitApplyBodySchema),
|
|
162
|
+
}, typed<ConnectKitApplyResult>()),
|
|
163
|
+
),
|
|
164
|
+
},
|
|
146
165
|
// Under `base` like every sibling — the guard and the ownership gate — and never under the
|
|
147
166
|
// paid gate: saving branding is free, and the paid credit switch is not reachable from here.
|
|
148
167
|
branding: {
|
|
@@ -5,7 +5,8 @@ import { connect } from './consts.js'
|
|
|
5
5
|
import type { ConnectOp, ConnectOpResult } from './ops.js'
|
|
6
6
|
import type {
|
|
7
7
|
ConnectAttachBody, ConnectConfirmBody, ConnectCreateBody, ConnectConvertCreateBody,
|
|
8
|
-
ConnectConvertProceedBody, ConnectInquiryAnswerBody,
|
|
8
|
+
ConnectConvertProceedBody, ConnectInquiryAnswerBody, ConnectKitApplyBody, ConnectKitApplyResult,
|
|
9
|
+
ConnectKitDescribe, ConnectModifyBody, ConnectOpSubmission,
|
|
9
10
|
ConnectPipelineParams, ConnectPipelineResumeBody, ConnectPipelineState, ConnectProjectBranding,
|
|
10
11
|
ConnectProjectBrandingSave, ConnectProjectStatus, ConnectProjectSummary, ConnectPullQuery,
|
|
11
12
|
ConnectSessionOpen, ConnectSessionParams, ConnectSessionView, ConnectStoryStatus,
|
|
@@ -35,6 +36,10 @@ export interface ConnectReferences {
|
|
|
35
36
|
attach: ConnectReference<{ body: ConnectAttachBody }, ConnectProjectStatus>
|
|
36
37
|
reinit: ConnectReference<{ params: { id: string } }, ConnectProjectStatus>
|
|
37
38
|
modify: ConnectReference<{ params: { id: string }, body: ConnectModifyBody }, ConnectProjectStatus>
|
|
39
|
+
kit: {
|
|
40
|
+
describe: ConnectReference<{ params: { id: string } }, ConnectKitDescribe>
|
|
41
|
+
apply: ConnectReference<{ params: { id: string }, body: ConnectKitApplyBody }, ConnectKitApplyResult>
|
|
42
|
+
}
|
|
38
43
|
branding: {
|
|
39
44
|
get: ConnectReference<{ params: { id: string } }, ConnectProjectBranding>
|
|
40
45
|
save: ConnectReference<{
|
|
@@ -96,6 +101,12 @@ export const connectRef: ConnectReferences = {
|
|
|
96
101
|
attach: entrypointRef<{ body: ConnectAttachBody }, ConnectProjectStatus>(connect.project.attach),
|
|
97
102
|
reinit: entrypointRef<{ params: { id: string } }, ConnectProjectStatus>(connect.project.reinit),
|
|
98
103
|
modify: entrypointRef<{ params: { id: string }, body: ConnectModifyBody }, ConnectProjectStatus>(connect.project.modify),
|
|
104
|
+
kit: {
|
|
105
|
+
describe: entrypointRef<{ params: { id: string } }, ConnectKitDescribe>(connect.project.kit.describe),
|
|
106
|
+
apply: entrypointRef<{
|
|
107
|
+
params: { id: string }, body: ConnectKitApplyBody
|
|
108
|
+
}, ConnectKitApplyResult>(connect.project.kit.apply),
|
|
109
|
+
},
|
|
99
110
|
branding: {
|
|
100
111
|
get: entrypointRef<{ params: { id: string } }, ConnectProjectBranding>(connect.project.branding.get),
|
|
101
112
|
save: entrypointRef<{
|
package/src/connect/schemas.ts
CHANGED
|
@@ -11,7 +11,8 @@ import type {
|
|
|
11
11
|
ConnectAttachBody, ConnectConfirmBody, ConnectConvertCreateBody, ConnectConvertProceedBody,
|
|
12
12
|
ConnectCreateBody, ConnectLlmBody, ConnectModifyBody, ConnectPipelineParams,
|
|
13
13
|
ConnectPipelineResumeBody, ConnectProjectBrandingSave, ConnectProjectLlmBody, ConnectSession,
|
|
14
|
-
ConnectSessionOpen, ConnectPullQuery, ConnectSessionParams
|
|
14
|
+
ConnectSessionOpen, ConnectPullQuery, ConnectSessionParams, ConnectKitApplyBody, ConnectKitApplyResult,
|
|
15
|
+
ConnectKitDescribe, PlanningKitView
|
|
15
16
|
} from './types.js'
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -202,6 +203,91 @@ export const ConnectModifyBodySchema = {
|
|
|
202
203
|
additionalProperties: false,
|
|
203
204
|
} as JSONSchemaType<ConnectModifyBody>
|
|
204
205
|
|
|
206
|
+
const kitKey = { type: 'string', minLength: 1, maxLength: 128 } as const
|
|
207
|
+
const kitText = { type: 'string', maxLength: 4096 } as const
|
|
208
|
+
const kitStrings = { type: 'array', items: kitKey, maxItems: 256 } as const
|
|
209
|
+
|
|
210
|
+
/** A planning kit as `project.kit.describe` lists it. Closed, like every view this package pins. */
|
|
211
|
+
export const PlanningKitViewSchema = {
|
|
212
|
+
type: 'object',
|
|
213
|
+
properties: {
|
|
214
|
+
id: kitKey,
|
|
215
|
+
kind: kitKey,
|
|
216
|
+
title: kitText,
|
|
217
|
+
purpose: kitText,
|
|
218
|
+
container: {
|
|
219
|
+
type: 'object',
|
|
220
|
+
properties: { key: kitKey, label: kitText },
|
|
221
|
+
required: ['key', 'label'],
|
|
222
|
+
additionalProperties: false,
|
|
223
|
+
},
|
|
224
|
+
types: {
|
|
225
|
+
type: 'array',
|
|
226
|
+
maxItems: 256,
|
|
227
|
+
items: {
|
|
228
|
+
type: 'object',
|
|
229
|
+
properties: { key: kitKey, label: kitText, flow: kitKey },
|
|
230
|
+
required: ['key', 'label', 'flow'],
|
|
231
|
+
additionalProperties: false,
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
flows: {
|
|
235
|
+
type: 'array',
|
|
236
|
+
maxItems: 256,
|
|
237
|
+
items: {
|
|
238
|
+
type: 'object',
|
|
239
|
+
properties: {
|
|
240
|
+
key: kitKey,
|
|
241
|
+
label: kitText,
|
|
242
|
+
statuses: {
|
|
243
|
+
type: 'array',
|
|
244
|
+
maxItems: 256,
|
|
245
|
+
items: {
|
|
246
|
+
type: 'object',
|
|
247
|
+
properties: { key: kitKey, label: kitText, intrinsic: kitKey },
|
|
248
|
+
required: ['key', 'label', 'intrinsic'],
|
|
249
|
+
additionalProperties: false,
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
required: ['key', 'label', 'statuses'],
|
|
254
|
+
additionalProperties: false,
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
required: ['id', 'kind', 'title', 'purpose', 'container', 'types', 'flows'],
|
|
259
|
+
additionalProperties: false,
|
|
260
|
+
} as unknown as JSONSchemaType<PlanningKitView>
|
|
261
|
+
|
|
262
|
+
export const ConnectKitDescribeSchema = {
|
|
263
|
+
type: 'object',
|
|
264
|
+
properties: { kits: { type: 'array', items: PlanningKitViewSchema, maxItems: 64 } },
|
|
265
|
+
required: ['kits'],
|
|
266
|
+
additionalProperties: false,
|
|
267
|
+
} as unknown as JSONSchemaType<ConnectKitDescribe>
|
|
268
|
+
|
|
269
|
+
/** `types` keeps only these card-type keys of the kit; omitted (or `null`) keeps every type. */
|
|
270
|
+
export const ConnectKitApplyBodySchema = {
|
|
271
|
+
type: 'object',
|
|
272
|
+
properties: {
|
|
273
|
+
kit: kitKey,
|
|
274
|
+
types: { ...kitStrings, nullable: true },
|
|
275
|
+
},
|
|
276
|
+
required: ['kit'],
|
|
277
|
+
additionalProperties: false,
|
|
278
|
+
} as unknown as JSONSchemaType<ConnectKitApplyBody>
|
|
279
|
+
|
|
280
|
+
export const ConnectKitApplyResultSchema = {
|
|
281
|
+
type: 'object',
|
|
282
|
+
properties: {
|
|
283
|
+
applied: kitStrings,
|
|
284
|
+
skipped: kitStrings,
|
|
285
|
+
warnings: { type: 'array', items: kitText, maxItems: 256 },
|
|
286
|
+
},
|
|
287
|
+
required: ['applied', 'skipped', 'warnings'],
|
|
288
|
+
additionalProperties: false,
|
|
289
|
+
} as unknown as JSONSchemaType<ConnectKitApplyResult>
|
|
290
|
+
|
|
205
291
|
export const ConnectAttachBodySchema = {
|
|
206
292
|
type: 'object',
|
|
207
293
|
properties: {
|
package/src/connect/types.ts
CHANGED
|
@@ -409,3 +409,58 @@ export interface ConnectPipelineState {
|
|
|
409
409
|
heartbeatAt: string
|
|
410
410
|
updatedAt: string
|
|
411
411
|
}
|
|
412
|
+
|
|
413
|
+
/** One status of a planning kit's flow. `intrinsic` is the planning intrinsic it maps to. */
|
|
414
|
+
export interface PlanningKitStatusView {
|
|
415
|
+
key: string
|
|
416
|
+
label: string
|
|
417
|
+
intrinsic: string
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** One status flow a planning kit declares. */
|
|
421
|
+
export interface PlanningKitFlowView {
|
|
422
|
+
key: string
|
|
423
|
+
label: string
|
|
424
|
+
statuses: PlanningKitStatusView[]
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** One card type a planning kit declares; `flow` is the key of its main flow. */
|
|
428
|
+
export interface PlanningKitTypeView {
|
|
429
|
+
key: string
|
|
430
|
+
label: string
|
|
431
|
+
flow: string
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* A planning kit as a connector sees it: a ready set of card types and status flows for one kind
|
|
436
|
+
* of work-management product, written into the target's common package on `apply`.
|
|
437
|
+
*/
|
|
438
|
+
export interface PlanningKitView {
|
|
439
|
+
id: string
|
|
440
|
+
/** The work kind the kit serves — a bare string, so a newer kind never fails an older reader. */
|
|
441
|
+
kind: string
|
|
442
|
+
title: string
|
|
443
|
+
purpose: string
|
|
444
|
+
/** The project-level container type the kit's cards live in. */
|
|
445
|
+
container: { key: string, label: string }
|
|
446
|
+
types: PlanningKitTypeView[]
|
|
447
|
+
flows: PlanningKitFlowView[]
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** What `project.kit.describe` answers. */
|
|
451
|
+
export interface ConnectKitDescribe {
|
|
452
|
+
kits: PlanningKitView[]
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/** Apply one kit; `types` keeps only these card-type keys of it (every type when omitted). */
|
|
456
|
+
export interface ConnectKitApplyBody {
|
|
457
|
+
kit: string
|
|
458
|
+
types?: string[]
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** What `project.kit.apply` answers: the type keys written, those left out, and warnings. */
|
|
462
|
+
export interface ConnectKitApplyResult {
|
|
463
|
+
applied: string[]
|
|
464
|
+
skipped: string[]
|
|
465
|
+
warnings: string[]
|
|
466
|
+
}
|
package/src/planning/schemas.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { JSONSchemaType } from 'ajv'
|
|
|
2
2
|
import { CODE_MAX } from '@owlmeans/planning'
|
|
3
3
|
import { ProjectArea } from '../areas/consts.js'
|
|
4
4
|
import { TENANCY_QUOTE_MAX } from '../areas/tenancy.js'
|
|
5
|
+
import { CASE_QUOTE_MAX, WorkKind } from '../blueprint/consts.js'
|
|
5
6
|
import { StoryKind } from '../ba/consts.js'
|
|
6
7
|
import { ConnectLlm, ConnectTarget } from '../connect/consts.js'
|
|
7
8
|
import { ProjectOriginSchema } from '../convert/schemas.js'
|
|
@@ -25,6 +26,8 @@ export const ViableProjectFieldsSchema = {
|
|
|
25
26
|
blueprint: { type: 'string', minLength: 1, maxLength: 64, nullable: true },
|
|
26
27
|
blueprintCase: { type: 'string', minLength: 1, maxLength: 64, nullable: true },
|
|
27
28
|
gameKind: { type: 'string', minLength: 1, maxLength: 64, nullable: true },
|
|
29
|
+
workKind: { type: 'string', enum: [...Object.values(WorkKind), null], nullable: true },
|
|
30
|
+
caseQuote: { type: 'string', minLength: 1, maxLength: CASE_QUOTE_MAX, nullable: true },
|
|
28
31
|
target: { type: 'string', enum: [...Object.values(ConnectTarget), null], nullable: true },
|
|
29
32
|
origin: { ...ProjectOriginSchema, nullable: true },
|
|
30
33
|
connectLlmMode: { type: 'string', enum: [...Object.values(ConnectLlm), null], nullable: true },
|
package/src/planning/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Card, Project, Specification } from '@owlmeans/planning'
|
|
2
2
|
import type { ProjectArea } from '../areas/consts.js'
|
|
3
|
+
import type { WorkKind } from '../blueprint/consts.js'
|
|
3
4
|
import type { ViableTenancyDecision } from '../areas/tenancy.js'
|
|
4
5
|
import type { StoryKind } from '../ba/consts.js'
|
|
5
6
|
import type { ConnectLlm, ConnectTarget } from '../connect/consts.js'
|
|
@@ -25,6 +26,10 @@ export type ViableProjectFields = {
|
|
|
25
26
|
blueprintCase?: string
|
|
26
27
|
/** How a game is played; absent for every other case. */
|
|
27
28
|
gameKind?: string
|
|
29
|
+
/** Which kind of work a work-management product keeps; absent for every other case. */
|
|
30
|
+
workKind?: WorkKind
|
|
31
|
+
/** The requester's sentence the work-management case was verified against. */
|
|
32
|
+
caseQuote?: string
|
|
28
33
|
/** Where the tree lives; absent is the platform's own slot. */
|
|
29
34
|
target?: ConnectTarget
|
|
30
35
|
/** Where converted code came from; present only on an import. */
|
package/src/slot/consts.ts
CHANGED
|
@@ -37,8 +37,24 @@ export enum SlotCommandType {
|
|
|
37
37
|
Files = 'files',
|
|
38
38
|
Shell = 'shell',
|
|
39
39
|
Git = 'git',
|
|
40
|
+
Database = 'database',
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
/** Inspection of the live dev database; mutations belong in target source migrations. */
|
|
44
|
+
export enum SlotDatabaseCommand {
|
|
45
|
+
Info = 'databaseInfo',
|
|
46
|
+
Query = 'databaseQuery',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const DATABASE_READ_LIMITS = {
|
|
50
|
+
defaultRows: 100,
|
|
51
|
+
maxRows: 1000,
|
|
52
|
+
maxBytes: 131_072,
|
|
53
|
+
maxQueryChars: 16_000,
|
|
54
|
+
maxParams: 100,
|
|
55
|
+
timeoutMs: 10_000,
|
|
56
|
+
} as const
|
|
57
|
+
|
|
42
58
|
export enum SlotGitCommand {
|
|
43
59
|
Ensure = 'ensure',
|
|
44
60
|
/**
|
|
@@ -306,6 +322,7 @@ export const COMMAND_TIMEOUTS: Partial<Record<SlotCommandType, number>> = {
|
|
|
306
322
|
[SlotCommandType.Files]: 60_000,
|
|
307
323
|
[SlotCommandType.Git]: 120_000,
|
|
308
324
|
[SlotCommandType.Shell]: 1_200_000,
|
|
325
|
+
[SlotCommandType.Database]: 45_000,
|
|
309
326
|
}
|
|
310
327
|
|
|
311
328
|
export const DEFAULT_COMMAND_TIMEOUT = 60_000
|
|
@@ -356,6 +373,7 @@ const perCommandTimeout = (type: SlotCommandType, command: string): number | und
|
|
|
356
373
|
switch (type) {
|
|
357
374
|
case SlotCommandType.Shell: return SHELL_COMMAND_TIMEOUTS[command]
|
|
358
375
|
case SlotCommandType.Git: return GIT_COMMAND_TIMEOUTS[command as SlotGitCommand]
|
|
376
|
+
case SlotCommandType.Database: return COMMAND_TIMEOUTS[SlotCommandType.Database]
|
|
359
377
|
default: return FILE_COMMAND_TIMEOUTS[command as SlotFileCommand]
|
|
360
378
|
}
|
|
361
379
|
}
|
|
@@ -387,6 +405,8 @@ export const commandDeadline = (type: SlotCommandType, command: string): number
|
|
|
387
405
|
return COMMAND_DEADLINES[command] ?? DEFAULT_COMMAND_DEADLINE
|
|
388
406
|
case SlotCommandType.Git:
|
|
389
407
|
return GIT_COMMAND_DEADLINES[command as SlotGitCommand] ?? DEFAULT_GIT_COMMAND_DEADLINE
|
|
408
|
+
case SlotCommandType.Database:
|
|
409
|
+
return 30_000
|
|
390
410
|
default:
|
|
391
411
|
return FILE_COMMAND_DEADLINES[command as SlotFileCommand] ?? DEFAULT_FILE_COMMAND_DEADLINE
|
|
392
412
|
}
|
package/src/slot/schemas.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { JSONSchemaType } from 'ajv'
|
|
2
|
-
import { SlotCommandType, SlotFileCommand, SlotGitCommand, SlotShellCommand } from './consts.js'
|
|
2
|
+
import { SlotCommandType, SlotDatabaseCommand, SlotFileCommand, SlotGitCommand, SlotShellCommand } from './consts.js'
|
|
3
3
|
import type { SlotCommandPayload, SlotGitCloneArgs } from './types.js'
|
|
4
4
|
|
|
5
5
|
export const SlotCommandPayloadSchema = {
|
|
@@ -14,6 +14,7 @@ export const SlotCommandPayloadSchema = {
|
|
|
14
14
|
{ type: 'string', enum: Object.values(SlotFileCommand) },
|
|
15
15
|
{ type: 'string', enum: Object.values(SlotShellCommand) },
|
|
16
16
|
{ type: 'string', enum: Object.values(SlotGitCommand) },
|
|
17
|
+
{ type: 'string', enum: Object.values(SlotDatabaseCommand) },
|
|
17
18
|
]
|
|
18
19
|
},
|
|
19
20
|
args: {
|
package/src/slot/types.ts
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
|
-
import type { SlotCommandType, SlotFileCommand, SlotGitCommand, SlotShellCommand, SubProject } from './consts.js'
|
|
1
|
+
import type { SlotCommandType, SlotDatabaseCommand, SlotFileCommand, SlotGitCommand, SlotShellCommand, SubProject } from './consts.js'
|
|
2
2
|
|
|
3
|
-
export type SlotCommand = SlotFileCommand | SlotShellCommand | SlotGitCommand
|
|
3
|
+
export type SlotCommand = SlotFileCommand | SlotShellCommand | SlotGitCommand | SlotDatabaseCommand
|
|
4
|
+
|
|
5
|
+
/** Driver identity is open-ended; inspect it before choosing a query language. No credentials. */
|
|
6
|
+
export interface SlotDatabaseInfo {
|
|
7
|
+
kind: string
|
|
8
|
+
queryLanguage: string
|
|
9
|
+
database: string
|
|
10
|
+
schema: string
|
|
11
|
+
role: string
|
|
12
|
+
version: string
|
|
13
|
+
readOnly: boolean
|
|
14
|
+
limits: { defaultRows: number; maxRows: number; maxBytes: number; maxQueryChars: number; maxParams: number; timeoutMs: number }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SlotDatabaseQueryArgs {
|
|
18
|
+
query: string
|
|
19
|
+
params?: Array<string | number | boolean | null>
|
|
20
|
+
maxRows?: number
|
|
21
|
+
timeoutMs?: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type SlotDatabaseQueryResult = {
|
|
25
|
+
ok: true
|
|
26
|
+
columns: Array<{ name: string; dataType: string }>
|
|
27
|
+
rows: Record<string, unknown>[]
|
|
28
|
+
truncated: boolean
|
|
29
|
+
} | {
|
|
30
|
+
ok: false
|
|
31
|
+
error: { message: string; code?: string; detail?: string; hint?: string; position?: string }
|
|
32
|
+
}
|
|
4
33
|
|
|
5
34
|
export interface SlotCommandPayload {
|
|
6
35
|
type: SlotCommandType
|
package/src/types.ts
CHANGED
|
@@ -7,6 +7,11 @@ export interface SlotMetadata extends
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export interface SlotConstMetadata {
|
|
10
|
+
/**
|
|
11
|
+
* The project's ALIAS — a lowercase slug, an identifier. The OIDC client id base, the key
|
|
12
|
+
* namespace prefix and the address are composed from it, so it never reaches a person as the
|
|
13
|
+
* product's name; that is the project card's `title`.
|
|
14
|
+
*/
|
|
10
15
|
projectName: string
|
|
11
16
|
oidcClientId: string
|
|
12
17
|
oidcRealm: string
|
package/tests/branding.spec.ts
CHANGED
|
@@ -25,18 +25,29 @@ describe('brandingEnv', () => {
|
|
|
25
25
|
|
|
26
26
|
it('composes the rest of the branding env from whatever is present, blank otherwise', () => {
|
|
27
27
|
expect(brandingEnv({
|
|
28
|
-
brandingCopyright: '© 2026 Acme', projectName: '
|
|
28
|
+
brandingCopyright: '© 2026 Acme', projectName: 'acme-crm',
|
|
29
29
|
})).toEqual({
|
|
30
30
|
BRANDING_COPYRIGHT: '© 2026 Acme',
|
|
31
31
|
BRANDING_ORGANIZATION: '',
|
|
32
32
|
BRANDING_TERMS_URL: '',
|
|
33
33
|
BRANDING_PRIVACY_URL: '',
|
|
34
34
|
BRANDING_CREDIT: '1',
|
|
35
|
-
BRANDING_PRODUCT: '
|
|
35
|
+
BRANDING_PRODUCT: '',
|
|
36
36
|
BRANDING_GOOGLE_TAG: '',
|
|
37
37
|
})
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
+
it('never delivers the project alias as the product name', () => {
|
|
41
|
+
// `projectName` is the lowercase slug the OIDC client and the address are composed from. Taken
|
|
42
|
+
// for the product's name it titled the document `slopepact` over a header saying `SlopePact`.
|
|
43
|
+
// The name a person reads is the target's own `APP_TITLE`, which the build falls back to when
|
|
44
|
+
// this is empty — and the key is still emitted, so the bundle has no bare `process.env` read.
|
|
45
|
+
for (const projectName of ['slopepact', 'Acme CRM', '']) {
|
|
46
|
+
expect(brandingEnv({ projectName }).BRANDING_PRODUCT).toBe('')
|
|
47
|
+
}
|
|
48
|
+
expect(Object.keys(brandingEnv({ projectName: 'slopepact' }))).toContain('BRANDING_PRODUCT')
|
|
49
|
+
})
|
|
50
|
+
|
|
40
51
|
it('carries the Google tag, and emits it empty when none is set', () => {
|
|
41
52
|
// The METADATA key is omitted from a push while unset (an older publisher refuses it); the
|
|
42
53
|
// ENVIRONMENT key is always there, and '' is what tells the build to load no tag.
|
|
@@ -4,7 +4,10 @@ import { aliasOf, protocols } from '@owlmeans/entrypoint'
|
|
|
4
4
|
import { DEFAULT_GUARD } from '@owlmeans/auth-common'
|
|
5
5
|
import { RouteMethod, RouteProtocols } from '@owlmeans/route'
|
|
6
6
|
import { connect, CONNECT_BRANDING_GOOGLE_TAG_MAX } from '../src/connect/consts.js'
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
ConnectKitApplyBodySchema, ConnectKitApplyResultSchema, ConnectKitDescribeSchema, ConnectProjectBrandingSaveSchema,
|
|
9
|
+
PlanningKitViewSchema,
|
|
10
|
+
} from '../src/connect/schemas.js'
|
|
8
11
|
import { connectProtocols } from '../src/connect/entrypoints.js'
|
|
9
12
|
import { connectRef } from '../src/connect/references.js'
|
|
10
13
|
|
|
@@ -18,8 +21,8 @@ describe('@owlmeans/viable-common — connector protocol tree', () => {
|
|
|
18
21
|
expect(tree.convert.proceed.alias).toBe(connect.convert.proceed)
|
|
19
22
|
expect(tree.inquiry.answer.alias).toBe(connect.inquiry.answer)
|
|
20
23
|
expect(tree.files.list.alias).toBe(connect.files.list)
|
|
21
|
-
expect(protocols(tree)).toHaveLength(
|
|
22
|
-
expect(new Set(protocols(tree).map(protocol => protocol.alias)).size).toBe(
|
|
24
|
+
expect(protocols(tree)).toHaveLength(28)
|
|
25
|
+
expect(new Set(protocols(tree).map(protocol => protocol.alias)).size).toBe(28)
|
|
23
26
|
})
|
|
24
27
|
|
|
25
28
|
test('declares exactly the routes a connector calls — every alias, reference and path, and no socket', () => {
|
|
@@ -29,11 +32,11 @@ describe('@owlmeans/viable-common — connector protocol tree', () => {
|
|
|
29
32
|
|
|
30
33
|
expect(addresses).toEqual([
|
|
31
34
|
'GET /convert/:id', 'GET /convert/:id/check', 'GET /pipeline/:id/:runId', 'GET /project',
|
|
32
|
-
'GET /project/:id/branding', 'GET /project/:id/files', 'GET /project/:id/status',
|
|
35
|
+
'GET /project/:id/branding', 'GET /project/:id/files', 'GET /project/:id/kits', 'GET /project/:id/status',
|
|
33
36
|
'GET /project/:id/story/:storyId/status', 'GET /session/:sessionId/ops',
|
|
34
37
|
'POST /convert', 'POST /convert/:id/proceed', 'POST /convert/:id/purge', 'POST /convert/:id/start',
|
|
35
38
|
'POST /pipeline/:id/:runId/resume', 'POST /project', 'POST /project/:id/branding',
|
|
36
|
-
'POST /project/:id/confirm', 'POST /project/:id/inquiry/:inquiryId', 'POST /project/:id/modify',
|
|
39
|
+
'POST /project/:id/confirm', 'POST /project/:id/inquiry/:inquiryId', 'POST /project/:id/kits', 'POST /project/:id/modify',
|
|
37
40
|
'POST /project/:id/reinit', 'POST /project/attach', 'POST /session', 'POST /session/:sessionId/close',
|
|
38
41
|
'POST /session/:sessionId/ops/:opId', 'POST /session/delegated',
|
|
39
42
|
])
|
|
@@ -94,4 +97,48 @@ describe('@owlmeans/viable-common — connector protocol tree', () => {
|
|
|
94
97
|
expect((connect.project as Record<string, unknown>).job).toBeUndefined()
|
|
95
98
|
expect((connectRef.project as Record<string, unknown>).job).toBeUndefined()
|
|
96
99
|
})
|
|
100
|
+
|
|
101
|
+
test('describes and applies planning kits at one project path, under the owned base and no paid gate', () => {
|
|
102
|
+
const paid = connectProtocols({
|
|
103
|
+
guard: DEFAULT_GUARD,
|
|
104
|
+
gate: { alias: 'test-gate', params: ['owner'] },
|
|
105
|
+
localLlm: { alias: 'test-paid-llm', params: ['cap'] },
|
|
106
|
+
})
|
|
107
|
+
const { describe: list, apply } = paid.project.kit
|
|
108
|
+
|
|
109
|
+
expect([list.alias, apply.alias]).toEqual([connect.project.kit.describe, connect.project.kit.apply])
|
|
110
|
+
expect([list.route.route.path, apply.route.route.path]).toEqual(['/project/:id/kits', '/project/:id/kits'])
|
|
111
|
+
expect([list.route.route.method, apply.route.route.method]).toEqual([RouteMethod.GET, RouteMethod.POST])
|
|
112
|
+
expect([aliasOf(list.route.route.parent), aliasOf(apply.route.route.parent)]).toEqual([connect.base, connect.base])
|
|
113
|
+
expect([list.gate, apply.gate]).toEqual([undefined, undefined])
|
|
114
|
+
expect([connectRef.project.kit.describe.alias, connectRef.project.kit.apply.alias])
|
|
115
|
+
.toEqual([connect.project.kit.describe, connect.project.kit.apply])
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('the kit view, the describe reply, the apply body and its result are closed shapes', () => {
|
|
119
|
+
const ajv = new Ajv({ strict: false })
|
|
120
|
+
const view = ajv.compile(PlanningKitViewSchema)
|
|
121
|
+
const reply = ajv.compile(ConnectKitDescribeSchema)
|
|
122
|
+
const body = ajv.compile(ConnectKitApplyBodySchema)
|
|
123
|
+
const result = ajv.compile(ConnectKitApplyResultSchema)
|
|
124
|
+
const kit = {
|
|
125
|
+
id: 'project', kind: 'project', title: 'Project tracking', purpose: 'Tasks moving to done',
|
|
126
|
+
container: { key: 'workspace', label: 'Project' },
|
|
127
|
+
types: [{ key: 'task', label: 'Task', flow: 'task' }],
|
|
128
|
+
flows: [{ key: 'task', label: 'Task', statuses: [{ key: 'open', label: 'Open', intrinsic: 'planned' }] }],
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
expect(view(kit)).toBe(true)
|
|
132
|
+
expect(view({ ...kit, extra: true })).toBe(false)
|
|
133
|
+
expect(view({ ...kit, types: [{ key: 'task', label: 'Task' }] })).toBe(false)
|
|
134
|
+
expect(reply({ kits: [kit] })).toBe(true)
|
|
135
|
+
expect(body({ kit: 'project' })).toBe(true)
|
|
136
|
+
expect(body({ kit: 'project', types: ['task', 'bug'] })).toBe(true)
|
|
137
|
+
expect(body({ kit: 'project', types: null })).toBe(true)
|
|
138
|
+
expect(body({ kit: '' })).toBe(false)
|
|
139
|
+
expect(body({ types: ['task'] })).toBe(false)
|
|
140
|
+
expect(body({ kit: 'project', force: true })).toBe(false)
|
|
141
|
+
expect(result({ applied: ['task'], skipped: ['bug'], warnings: [] })).toBe(true)
|
|
142
|
+
expect(result({ applied: ['task'], skipped: [] })).toBe(false)
|
|
143
|
+
})
|
|
97
144
|
})
|
package/tests/planning.spec.ts
CHANGED
|
@@ -263,6 +263,14 @@ describe('viable-common - the viable field schemas', () => {
|
|
|
263
263
|
expect(project({ alias: 'slot-booker' })).toBe(false)
|
|
264
264
|
})
|
|
265
265
|
|
|
266
|
+
test('a project records a work kind from the closed set and a bounded case quote', () => {
|
|
267
|
+
expect(project({ blueprintCase: 'work-management', workKind: 'crm', caseQuote: 'A CRM for ski resorts.' })).toBe(true)
|
|
268
|
+
expect(project({ workKind: null, caseQuote: null })).toBe(true)
|
|
269
|
+
expect(project({ workKind: 'kanban' })).toBe(false)
|
|
270
|
+
expect(project({ caseQuote: '' })).toBe(false)
|
|
271
|
+
expect(project({ caseQuote: 'x'.repeat(1025) })).toBe(false)
|
|
272
|
+
})
|
|
273
|
+
|
|
266
274
|
test('a story carries the landing flag, spelled or nulled', () => {
|
|
267
275
|
expect(story({ area: ProjectArea.User, primary: false, landing: true })).toBe(true)
|
|
268
276
|
expect(story({ area: ProjectArea.User, primary: false, landing: null })).toBe(true)
|