@owlmeans/viable-common 0.0.41 → 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 +2 -2
- 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 +9 -6
- 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 +2 -2
- 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
package/src/blueprint/consts.ts
CHANGED
|
@@ -34,6 +34,16 @@ export enum BlueprintCase {
|
|
|
34
34
|
AiAgent = 'ai-agent',
|
|
35
35
|
/** A browser game: a three.js scene with an ordinary React interface over it. */
|
|
36
36
|
Game = 'game',
|
|
37
|
+
/**
|
|
38
|
+
* Work management: the product's core is records of work people create and move through
|
|
39
|
+
* statuses or a pipeline, built on the planning packages. Single organization.
|
|
40
|
+
*/
|
|
41
|
+
WorkManagement = 'work-management',
|
|
42
|
+
/**
|
|
43
|
+
* Work management whose audiences work inside many organizations. Never classified directly:
|
|
44
|
+
* code picks it from {@link WorkManagement} when the project's tenancy decision has a flag on.
|
|
45
|
+
*/
|
|
46
|
+
WorkManagementTenanted = 'work-management-tenanted',
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
/**
|
|
@@ -49,3 +59,29 @@ export enum GameKind {
|
|
|
49
59
|
OnlineTurn = 'online-turn',
|
|
50
60
|
OnlineLive = 'online-live',
|
|
51
61
|
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Which kind of work a work-management product keeps — read by the planning kit and the analysis.
|
|
65
|
+
*
|
|
66
|
+
* Recorded beside the case on the project card (`fields.workKind`), like {@link GameKind}: it
|
|
67
|
+
* changes no dependency, only which ready-made card types and flows the product starts from.
|
|
68
|
+
*/
|
|
69
|
+
export enum WorkKind {
|
|
70
|
+
/** Projects broken into tasks or issues moved across stages to done. */
|
|
71
|
+
Project = 'project',
|
|
72
|
+
/** Leads, contacts, accounts and opportunities through a sales pipeline. */
|
|
73
|
+
Crm = 'crm',
|
|
74
|
+
/** Requests become tickets assigned to agents and worked to resolution. */
|
|
75
|
+
ServiceDesk = 'service-desk',
|
|
76
|
+
/** Items and stock across locations; orders and movements through fulfilment stages. */
|
|
77
|
+
Inventory = 'inventory',
|
|
78
|
+
/** Candidates and applications through a hiring workflow per opening. */
|
|
79
|
+
Recruiting = 'recruiting',
|
|
80
|
+
/** Jobs dispatched to field workers and tracked to completion. */
|
|
81
|
+
FieldService = 'field-service',
|
|
82
|
+
/** Requests, approvals or cases through a defined multi-step process. */
|
|
83
|
+
Process = 'process',
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The longest case quote a project card keeps (`fields.caseQuote`) — one sentence, generously. */
|
|
87
|
+
export const CASE_QUOTE_MAX = 1024
|
package/src/blueprint/types.ts
CHANGED
|
@@ -166,6 +166,13 @@ export interface TemplateLayer {
|
|
|
166
166
|
patches: BlueprintPatchSpec[]
|
|
167
167
|
/** Overlay subtrees that belong to one role only — the per-package half of a template. */
|
|
168
168
|
perRole?: Partial<Record<SubProject, string>>
|
|
169
|
+
/**
|
|
170
|
+
* Case seed directories under the overlay's `.cases/`, copied over the staged tree in order.
|
|
171
|
+
*
|
|
172
|
+
* Names, never paths: the host resolves them against the overlay it resolved. A later seed wins
|
|
173
|
+
* over an earlier one, so a variant lists its base seed first and itself after it.
|
|
174
|
+
*/
|
|
175
|
+
seeds?: string[]
|
|
169
176
|
}
|
|
170
177
|
|
|
171
178
|
export interface BlueprintPatchSpec {
|
package/src/branding.ts
CHANGED
|
@@ -25,6 +25,9 @@ export const BRANDING_ENV_KEYS = [
|
|
|
25
25
|
* safe direction — the opposite failure hands out a paid feature and nobody reports getting more
|
|
26
26
|
* than they paid for.
|
|
27
27
|
*
|
|
28
|
+
* `BRANDING_PRODUCT` is always EMITTED empty: the product's name is the target's own `APP_TITLE`,
|
|
29
|
+
* stated from the project card, and the slot metadata carries no human-readable name to deliver.
|
|
30
|
+
*
|
|
28
31
|
* `BRANDING_GOOGLE_TAG` is always EMITTED, empty when no tag is set, although its metadata key is
|
|
29
32
|
* omitted from a push while unset: the environment is read by the application's own build, which
|
|
30
33
|
* treats `''` as "load no tag", while the metadata key is read by a publisher that may predate it.
|
|
@@ -35,6 +38,13 @@ export const brandingEnv = (meta: Partial<SlotMetadata>): Record<string, string>
|
|
|
35
38
|
BRANDING_TERMS_URL: meta.brandingTermsUrl ?? '',
|
|
36
39
|
BRANDING_PRIVACY_URL: meta.brandingPrivacyUrl ?? '',
|
|
37
40
|
BRANDING_CREDIT: meta.brandingCredit === '' ? '' : '1',
|
|
38
|
-
|
|
41
|
+
// Never `projectName`. That is the project's ALIAS — a lowercase slug the OIDC client id, the
|
|
42
|
+
// key namespace and the address are composed from — and a build that took it for the product's
|
|
43
|
+
// name put `slopepact` in the tab, the manifest and the legal pages beside a header saying
|
|
44
|
+
// `SlopePact`. The human-readable name is the project card's title, which the platform states in
|
|
45
|
+
// the target's own `APP_TITLE`; every page reads that, and the build falls back to it whenever
|
|
46
|
+
// this is empty. The key stays, always emitted, so a bundle never carries a bare `process.env`
|
|
47
|
+
// read — and so an explicit override still has somewhere to arrive.
|
|
48
|
+
BRANDING_PRODUCT: '',
|
|
39
49
|
BRANDING_GOOGLE_TAG: meta.brandingGoogleTag ?? '',
|
|
40
50
|
})
|
package/src/connect/consts.ts
CHANGED
|
@@ -394,6 +394,14 @@ export const connect = Object.freeze({
|
|
|
394
394
|
attach: 'viable:manager-api:connect:project:attach',
|
|
395
395
|
reinit: 'viable:manager-api:connect:project:reinit',
|
|
396
396
|
modify: 'viable:manager-api:connect:project:modify',
|
|
397
|
+
/**
|
|
398
|
+
* Planning kits — ready sets of card types and status flows the platform writes into a
|
|
399
|
+
* target's common package (`describe` lists them, `apply` writes one and rebuilds the slot).
|
|
400
|
+
*/
|
|
401
|
+
kit: Object.freeze({
|
|
402
|
+
describe: 'viable:manager-api:connect:project:kit:describe',
|
|
403
|
+
apply: 'viable:manager-api:connect:project:kit:apply',
|
|
404
|
+
}),
|
|
397
405
|
/**
|
|
398
406
|
* The project's own branding — copyright, organization, the two legal links, the Google tag.
|
|
399
407
|
* The platform credit is deliberately NOT here: hiding it is a paid capability with its own
|
|
@@ -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)
|