@owlmeans/viable-common 0.0.39 → 0.0.41
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 +4 -4
- 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 +331 -391
- package/build/areas/index.d.ts +1 -0
- package/build/areas/index.d.ts.map +1 -1
- package/build/areas/index.js +1 -0
- package/build/areas/index.js.map +1 -1
- package/build/areas/tenancy.d.ts +55 -0
- package/build/areas/tenancy.d.ts.map +1 -0
- package/build/areas/tenancy.js +33 -0
- package/build/areas/tenancy.js.map +1 -0
- package/build/blueprint/types.d.ts +19 -1
- package/build/blueprint/types.d.ts.map +1 -1
- package/build/blueprint/types.js.map +1 -1
- package/build/connect/consts.d.ts +3 -15
- package/build/connect/consts.d.ts.map +1 -1
- package/build/connect/consts.js +3 -15
- package/build/connect/consts.js.map +1 -1
- package/build/connect/entrypoints.d.ts +33 -69
- package/build/connect/entrypoints.d.ts.map +1 -1
- package/build/connect/entrypoints.js +5 -25
- package/build/connect/entrypoints.js.map +1 -1
- package/build/connect/references.d.ts +1 -31
- package/build/connect/references.d.ts.map +1 -1
- package/build/connect/references.js +0 -7
- package/build/connect/references.js.map +1 -1
- package/build/connect/types.d.ts +1 -25
- package/build/connect/types.d.ts.map +1 -1
- package/build/dev/consts.d.ts +15 -0
- package/build/dev/consts.d.ts.map +1 -1
- package/build/dev/consts.js +16 -0
- package/build/dev/consts.js.map +1 -1
- package/build/dev/schemas.d.ts +7 -0
- package/build/dev/schemas.d.ts.map +1 -1
- package/build/dev/schemas.js +9 -1
- package/build/dev/schemas.js.map +1 -1
- package/build/dev/types.d.ts +15 -1
- package/build/dev/types.d.ts.map +1 -1
- package/build/planning/schemas.d.ts.map +1 -1
- package/build/planning/schemas.js +25 -0
- package/build/planning/schemas.js.map +1 -1
- package/build/planning/types.d.ts +9 -0
- package/build/planning/types.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/areas/index.ts +1 -0
- package/src/areas/tenancy.ts +69 -0
- package/src/blueprint/types.ts +19 -1
- package/src/connect/consts.ts +3 -16
- package/src/connect/entrypoints.ts +10 -60
- package/src/connect/references.ts +4 -26
- package/src/connect/types.ts +1 -22
- package/src/dev/consts.ts +16 -0
- package/src/dev/schemas.ts +13 -1
- package/src/dev/types.ts +15 -1
- package/src/planning/schemas.ts +25 -0
- package/src/planning/types.ts +9 -0
- package/tests/access-schema.spec.ts +37 -0
- package/tests/connect-convert.spec.ts +5 -11
- package/tests/connect-entrypoints.spec.ts +33 -14
- package/tests/planning.spec.ts +26 -0
- package/tests/tenancy.spec.ts +48 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
2
|
import Ajv from 'ajv'
|
|
3
3
|
import addFormats from 'ajv-formats'
|
|
4
|
-
import { RouteMethod
|
|
5
|
-
import { aliasOf,
|
|
4
|
+
import { RouteMethod } from '@owlmeans/route'
|
|
5
|
+
import { aliasOf, protocols } from '@owlmeans/entrypoint'
|
|
6
6
|
import { connect, ConnectInquiryKind, ConnectOpKind } from '../src/connect/consts.js'
|
|
7
7
|
import { connectProtocols } from '../src/connect/entrypoints.js'
|
|
8
8
|
import { connectRef } from '../src/connect/references.js'
|
|
@@ -20,7 +20,6 @@ const entrypoints = () => connectProtocols({
|
|
|
20
20
|
guard: 'test-guard',
|
|
21
21
|
gate: { alias: 'test-gate', params: ['id'] },
|
|
22
22
|
localLlm: { alias: 'test-paid-llm', params: ['id'] },
|
|
23
|
-
updateBase: openProtocol(route('test:update:base', '/update')),
|
|
24
23
|
})
|
|
25
24
|
|
|
26
25
|
const entrypointOf = (alias: string) => {
|
|
@@ -102,11 +101,9 @@ describe('viable-common - the conversion additions to the connector contract', (
|
|
|
102
101
|
[connect.convert.check, '/convert/:id/check', RouteMethod.GET],
|
|
103
102
|
[connect.convert.start, '/convert/:id/start', RouteMethod.POST],
|
|
104
103
|
[connect.convert.proceed, '/convert/:id/proceed', RouteMethod.POST],
|
|
105
|
-
[connect.convert.cancel, '/convert/:id/cancel', RouteMethod.POST],
|
|
106
104
|
[connect.convert.status, '/convert/:id', RouteMethod.GET],
|
|
107
105
|
[connect.convert.purge, '/convert/:id/purge', RouteMethod.POST],
|
|
108
106
|
[connect.inquiry.answer, '/project/:id/inquiry/:inquiryId', RouteMethod.POST],
|
|
109
|
-
[connect.project.converterLlm, '/project/:id/converter-llm', RouteMethod.POST],
|
|
110
107
|
[connect.files.list, '/project/:id/files', RouteMethod.GET],
|
|
111
108
|
]
|
|
112
109
|
|
|
@@ -122,21 +119,18 @@ describe('viable-common - the conversion additions to the connector contract', (
|
|
|
122
119
|
expect(connectRef.convert.check.alias).toBe(connect.convert.check)
|
|
123
120
|
expect(connectRef.convert.start.alias).toBe(connect.convert.start)
|
|
124
121
|
expect(connectRef.convert.proceed.alias).toBe(connect.convert.proceed)
|
|
125
|
-
expect(connectRef.convert.cancel.alias).toBe(connect.convert.cancel)
|
|
126
122
|
expect(connectRef.convert.status.alias).toBe(connect.convert.status)
|
|
127
123
|
expect(connectRef.convert.purge.alias).toBe(connect.convert.purge)
|
|
128
124
|
expect(connectRef.inquiry.answer.alias).toBe(connect.inquiry.answer)
|
|
129
|
-
expect(connectRef.project.converterLlm.alias).toBe(connect.project.converterLlm)
|
|
130
125
|
expect(connectRef.files.list.alias).toBe(connect.files.list)
|
|
131
126
|
})
|
|
132
127
|
|
|
133
128
|
test('the conversion routes hang under the connector base and carry no paid gate', () => {
|
|
134
129
|
// Delegated inference is the DEFAULT for a conversion, not an experimental capability — so
|
|
135
|
-
// unlike
|
|
136
|
-
expect(entrypointOf(connect.
|
|
130
|
+
// unlike the delegated session, nothing here sits behind the local-LLM gate.
|
|
131
|
+
expect(entrypointOf(connect.session.openDelegated).gate?.alias).toBe('test-paid-llm')
|
|
137
132
|
|
|
138
|
-
const gated = [...Object.values(connect.convert), connect.inquiry.answer
|
|
139
|
-
connect.project.converterLlm]
|
|
133
|
+
const gated = [...Object.values(connect.convert), connect.inquiry.answer]
|
|
140
134
|
for (const alias of gated) {
|
|
141
135
|
expect(entrypointOf(alias).gate, alias).toBeUndefined()
|
|
142
136
|
expect(aliasOf(routeOf(alias).parent!), alias).toBe(connect.base)
|
|
@@ -1,30 +1,48 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
2
|
import Ajv from 'ajv'
|
|
3
|
-
import { aliasOf,
|
|
3
|
+
import { aliasOf, protocols } from '@owlmeans/entrypoint'
|
|
4
4
|
import { DEFAULT_GUARD } from '@owlmeans/auth-common'
|
|
5
|
-
import {
|
|
5
|
+
import { RouteMethod, RouteProtocols } from '@owlmeans/route'
|
|
6
6
|
import { connect, CONNECT_BRANDING_GOOGLE_TAG_MAX } from '../src/connect/consts.js'
|
|
7
7
|
import { ConnectProjectBrandingSaveSchema } from '../src/connect/schemas.js'
|
|
8
8
|
import { connectProtocols } from '../src/connect/entrypoints.js'
|
|
9
9
|
import { connectRef } from '../src/connect/references.js'
|
|
10
10
|
|
|
11
11
|
describe('@owlmeans/viable-common — connector protocol tree', () => {
|
|
12
|
-
const tree = connectProtocols({
|
|
13
|
-
guard: DEFAULT_GUARD,
|
|
14
|
-
updateBase: openProtocol(route('manager:update', '/update')),
|
|
15
|
-
})
|
|
12
|
+
const tree = connectProtocols({ guard: DEFAULT_GUARD })
|
|
16
13
|
|
|
17
14
|
test('exports named immutable declarations and a complete flat materialization view', () => {
|
|
18
15
|
expect(tree.base.alias).toBe(connect.base)
|
|
19
16
|
expect(tree.session.open.alias).toBe(connect.session.open)
|
|
20
|
-
expect(tree.project.llm.alias).toBe(connect.project.llm)
|
|
21
|
-
expect(tree.project.converterLlm.alias).toBe(connect.project.converterLlm)
|
|
22
17
|
expect(tree.story.status.alias).toBe(connect.story.status)
|
|
23
18
|
expect(tree.convert.proceed.alias).toBe(connect.convert.proceed)
|
|
24
19
|
expect(tree.inquiry.answer.alias).toBe(connect.inquiry.answer)
|
|
25
20
|
expect(tree.files.list.alias).toBe(connect.files.list)
|
|
26
|
-
expect(protocols(tree)).toHaveLength(
|
|
27
|
-
expect(new Set(protocols(tree).map(protocol => protocol.alias)).size).toBe(
|
|
21
|
+
expect(protocols(tree)).toHaveLength(26)
|
|
22
|
+
expect(new Set(protocols(tree).map(protocol => protocol.alias)).size).toBe(26)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('declares exactly the routes a connector calls — every alias, reference and path, and no socket', () => {
|
|
26
|
+
const declared = protocols(tree)
|
|
27
|
+
const addresses = declared.filter(protocol => protocol.route.route.method != null)
|
|
28
|
+
.map(protocol => `${protocol.route.route.method!.toUpperCase()} ${protocol.route.route.path}`).sort()
|
|
29
|
+
|
|
30
|
+
expect(addresses).toEqual([
|
|
31
|
+
'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',
|
|
33
|
+
'GET /project/:id/story/:storyId/status', 'GET /session/:sessionId/ops',
|
|
34
|
+
'POST /convert', 'POST /convert/:id/proceed', 'POST /convert/:id/purge', 'POST /convert/:id/start',
|
|
35
|
+
'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',
|
|
37
|
+
'POST /project/:id/reinit', 'POST /project/attach', 'POST /session', 'POST /session/:sessionId/close',
|
|
38
|
+
'POST /session/:sessionId/ops/:opId', 'POST /session/delegated',
|
|
39
|
+
])
|
|
40
|
+
expect(declared.every(protocol => protocol.route.route.protocol !== RouteProtocols.SOCKET)).toBe(true)
|
|
41
|
+
// The alias table, the tree and the typed references name the same routes.
|
|
42
|
+
const leaves = (value: object): string[] => Object.values(value).flatMap(entry =>
|
|
43
|
+
typeof entry === 'string' ? [entry] : 'alias' in entry ? [entry.alias as string] : leaves(entry))
|
|
44
|
+
expect(leaves(connect).sort()).toEqual(declared.map(protocol => protocol.alias).sort())
|
|
45
|
+
expect(leaves(connectRef).sort()).toEqual(declared.map(protocol => protocol.alias).filter(alias => alias !== connect.base).sort())
|
|
28
46
|
})
|
|
29
47
|
|
|
30
48
|
test('reads and saves a project\'s branding at one path, under the owned base and no paid gate', () => {
|
|
@@ -32,7 +50,6 @@ describe('@owlmeans/viable-common — connector protocol tree', () => {
|
|
|
32
50
|
guard: DEFAULT_GUARD,
|
|
33
51
|
gate: { alias: 'test-gate', params: ['owner'] },
|
|
34
52
|
localLlm: { alias: 'test-paid-llm', params: ['cap'] },
|
|
35
|
-
updateBase: openProtocol(route('manager:update', '/update')),
|
|
36
53
|
})
|
|
37
54
|
const get = paid.project.branding.get
|
|
38
55
|
const save = paid.project.branding.save
|
|
@@ -45,9 +62,11 @@ describe('@owlmeans/viable-common — connector protocol tree', () => {
|
|
|
45
62
|
expect(save.route.route.method).toBe(RouteMethod.POST)
|
|
46
63
|
expect(aliasOf(get.route.route.parent)).toBe(connect.base)
|
|
47
64
|
expect(aliasOf(save.route.route.parent)).toBe(connect.base)
|
|
48
|
-
// The paid local-LLM gate sits on
|
|
49
|
-
// inherits only the base's guard and ownership gate.
|
|
50
|
-
expect(paid.
|
|
65
|
+
// The paid local-LLM gate sits on the delegated session alone; branding carries no gate of its
|
|
66
|
+
// own and inherits only the base's guard and ownership gate.
|
|
67
|
+
expect(paid.session.openDelegated.gate?.alias).toBe('test-paid-llm')
|
|
68
|
+
expect(protocols(paid).filter(protocol => protocol.gate?.alias === 'test-paid-llm'))
|
|
69
|
+
.toEqual([paid.session.openDelegated])
|
|
51
70
|
expect(get.gate).toBeUndefined()
|
|
52
71
|
expect(save.gate).toBeUndefined()
|
|
53
72
|
expect(connectRef.project.branding.save.alias).toBe(connect.project.branding.save)
|
package/tests/planning.spec.ts
CHANGED
|
@@ -276,6 +276,32 @@ describe('viable-common - the viable field schemas', () => {
|
|
|
276
276
|
expect(project({ landing: { story: 'US-ABC12' } })).toBe(false)
|
|
277
277
|
expect(project({ landing: { story: 'US-ABC12', at, reason: 'best fit' } })).toBe(false)
|
|
278
278
|
})
|
|
279
|
+
|
|
280
|
+
test('a project records a tenancy decision with both flags, who decided it and when', () => {
|
|
281
|
+
expect(project({ tenancy: { operators: false, users: true, by: 'model', at } })).toBe(true)
|
|
282
|
+
expect(project({
|
|
283
|
+
tenancy: {
|
|
284
|
+
operators: true, users: true, by: 'owner', at,
|
|
285
|
+
quotes: { operators: 'Each agency manages its own staff.', users: null },
|
|
286
|
+
},
|
|
287
|
+
})).toBe(true)
|
|
288
|
+
expect(project({ tenancy: null })).toBe(true)
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
test('a project refuses a partial tenancy decision, an unknown decider and a stray key', () => {
|
|
292
|
+
expect(project({ tenancy: { users: true, by: 'model', at } })).toBe(false)
|
|
293
|
+
expect(project({ tenancy: { operators: true, users: false, at } })).toBe(false)
|
|
294
|
+
expect(project({ tenancy: { operators: true, users: false, by: 'model' } })).toBe(false)
|
|
295
|
+
expect(project({ tenancy: { operators: 'yes', users: false, by: 'model', at } })).toBe(false)
|
|
296
|
+
expect(project({ tenancy: { operators: true, users: false, by: 'agent', at } })).toBe(false)
|
|
297
|
+
expect(project({ tenancy: { operators: true, users: false, by: 'model', at, reason: 'b2b' } })).toBe(false)
|
|
298
|
+
expect(project({
|
|
299
|
+
tenancy: { operators: true, users: false, by: 'model', at, quotes: { staff: 'x' } },
|
|
300
|
+
})).toBe(false)
|
|
301
|
+
expect(project({
|
|
302
|
+
tenancy: { operators: true, users: false, by: 'model', at, quotes: { operators: 'x'.repeat(1025) } },
|
|
303
|
+
})).toBe(false)
|
|
304
|
+
})
|
|
279
305
|
})
|
|
280
306
|
|
|
281
307
|
describe('viable-common - the viable card helpers', () => {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { NO_TENANCY, ProjectArea, tenancyOf, tenantedArea } from '../src/index.js'
|
|
3
|
+
import { LandingGatePreference } from '../src/blueprint/index.js'
|
|
4
|
+
|
|
5
|
+
const experience = (tenancy: unknown) =>
|
|
6
|
+
({ experience: { landingGate: LandingGatePreference.Allow, tenancy: tenancy as never } })
|
|
7
|
+
|
|
8
|
+
describe('viable-common - the project tenancy', () => {
|
|
9
|
+
test('NO_TENANCY is both flags off and cannot be changed', () => {
|
|
10
|
+
expect(NO_TENANCY).toEqual({ operators: false, users: false })
|
|
11
|
+
expect(Object.isFrozen(NO_TENANCY)).toBe(true)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('tenancyOf answers NO_TENANCY for no blueprint, no layer, no key and an unreadable value', () => {
|
|
15
|
+
expect(tenancyOf()).toBe(NO_TENANCY)
|
|
16
|
+
expect(tenancyOf(null)).toBe(NO_TENANCY)
|
|
17
|
+
expect(tenancyOf({})).toBe(NO_TENANCY)
|
|
18
|
+
expect(tenancyOf({ experience: { landingGate: LandingGatePreference.Allow } })).toBe(NO_TENANCY)
|
|
19
|
+
expect(tenancyOf(experience(null))).toBe(NO_TENANCY)
|
|
20
|
+
expect(tenancyOf(experience('both'))).toBe(NO_TENANCY)
|
|
21
|
+
expect(tenancyOf(experience({ operators: 'yes', users: 1 }))).toBe(NO_TENANCY)
|
|
22
|
+
expect(tenancyOf(experience({ operators: false, users: false }))).toBe(NO_TENANCY)
|
|
23
|
+
expect(tenancyOf({ experience: 'flat' as never })).toBe(NO_TENANCY)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('tenancyOf reads a literal true per flag, and an absent flag as off', () => {
|
|
27
|
+
expect(tenancyOf(experience({ operators: true, users: false }))).toEqual({ operators: true, users: false })
|
|
28
|
+
expect(tenancyOf(experience({ users: true }))).toEqual({ operators: false, users: true })
|
|
29
|
+
const both = tenancyOf(experience({ operators: true, users: true }))
|
|
30
|
+
expect(both).toEqual({ operators: true, users: true })
|
|
31
|
+
expect(Object.isFrozen(both)).toBe(true)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('tenantedArea: user follows users, operator follows operators, guest and admin never', () => {
|
|
35
|
+
const all = { operators: true, users: true }
|
|
36
|
+
expect(tenantedArea(ProjectArea.User, all)).toBe(true)
|
|
37
|
+
expect(tenantedArea(ProjectArea.Operator, all)).toBe(true)
|
|
38
|
+
expect(tenantedArea(ProjectArea.Guest, all)).toBe(false)
|
|
39
|
+
expect(tenantedArea(ProjectArea.Admin, all)).toBe(false)
|
|
40
|
+
|
|
41
|
+
expect(tenantedArea(ProjectArea.User, { operators: true, users: false })).toBe(false)
|
|
42
|
+
expect(tenantedArea(ProjectArea.Operator, { operators: false, users: true })).toBe(false)
|
|
43
|
+
for (const area of Object.values(ProjectArea)) {
|
|
44
|
+
expect(tenantedArea(area, NO_TENANCY)).toBe(false)
|
|
45
|
+
}
|
|
46
|
+
expect(tenantedArea('partner' as ProjectArea, all)).toBe(false)
|
|
47
|
+
})
|
|
48
|
+
})
|