@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
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Blueprint } from '../blueprint/types.js'
|
|
2
|
+
import { ProjectArea } from './consts.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Whether a generated application splits its audiences into tenant ORGANIZATIONS.
|
|
6
|
+
*
|
|
7
|
+
* Two flags rather than one, because the two audiences split independently: one back office can
|
|
8
|
+
* serve end users who each belong to their own company (`users` only), every reseller can run its
|
|
9
|
+
* own staff over one shared public (`operators` only), and a B2B tool usually does both. An
|
|
10
|
+
* organization is an IAM-managed tenant — never a table the target keeps for itself.
|
|
11
|
+
*
|
|
12
|
+
* Both false is today's application: one organization, the project owner's, that every person acts in.
|
|
13
|
+
*/
|
|
14
|
+
export interface ProjectTenancy {
|
|
15
|
+
/** The staff of the back office are split into tenant organizations. */
|
|
16
|
+
operators: boolean
|
|
17
|
+
/** The end users of the front office are distributed across tenant organizations. */
|
|
18
|
+
users: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A tenancy decision as the project card records it (`fields.tenancy`).
|
|
23
|
+
*
|
|
24
|
+
* The flags plus where they came from. `quotes` are the requester's OWN sentences, verbatim — the
|
|
25
|
+
* evidence a later reader (a person reviewing the board, a run asked to reconsider) checks the
|
|
26
|
+
* decision against, which a paraphrase would quietly replace with the model's reading of it.
|
|
27
|
+
* `by` says whether a model inferred it or the owner set it; `at` is when, an ISO timestamp.
|
|
28
|
+
*/
|
|
29
|
+
export interface ViableTenancyDecision extends ProjectTenancy {
|
|
30
|
+
quotes?: { operators?: string; users?: string }
|
|
31
|
+
by: 'model' | 'owner'
|
|
32
|
+
at: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The longest requester sentence a tenancy decision quotes — a card refuses a longer one. */
|
|
36
|
+
export const TENANCY_QUOTE_MAX = 1024
|
|
37
|
+
|
|
38
|
+
/** A single-organization application — what every project is until a decision says otherwise. */
|
|
39
|
+
export const NO_TENANCY: ProjectTenancy = Object.freeze({ operators: false, users: false })
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The tenancy of a resolved blueprint.
|
|
43
|
+
*
|
|
44
|
+
* Total, like `landingGatePreferenceOf`: an absent layer, an absent key and a value this deploy
|
|
45
|
+
* cannot read all answer {@link NO_TENANCY}. Only a literal `true` turns a flag on, because a flag
|
|
46
|
+
* read as on by mistake splits a product's people into organizations nobody asked for, while one
|
|
47
|
+
* read as off leaves the application exactly as it was before tenancy existed.
|
|
48
|
+
*/
|
|
49
|
+
export const tenancyOf = (blueprint?: Pick<Blueprint, 'experience'> | null): ProjectTenancy => {
|
|
50
|
+
const tenancy = blueprint?.experience?.tenancy
|
|
51
|
+
const operators = tenancy?.operators === true
|
|
52
|
+
const users = tenancy?.users === true
|
|
53
|
+
|
|
54
|
+
return operators || users ? Object.freeze({ operators, users }) : NO_TENANCY
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Whether an AREA's people act inside a tenant organization.
|
|
59
|
+
*
|
|
60
|
+
* Guest has no organization to act in, and admin is the project owner, who stands above every
|
|
61
|
+
* tenant rather than inside one — so both are never tenanted, whatever the flags say.
|
|
62
|
+
*/
|
|
63
|
+
export const tenantedArea = (area: ProjectArea, tenancy: ProjectTenancy): boolean => {
|
|
64
|
+
switch (area) {
|
|
65
|
+
case ProjectArea.User: return tenancy.users === true
|
|
66
|
+
case ProjectArea.Operator: return tenancy.operators === true
|
|
67
|
+
default: return false
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/blueprint/types.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ProjectTenancy } from '../areas/tenancy.js'
|
|
1
2
|
import type { SubProject } from '../slot/consts.js'
|
|
2
3
|
import type { ViableSkill } from '../skills/consts.js'
|
|
3
4
|
import type { ViablePersona } from '../skills/roles.js'
|
|
@@ -21,7 +22,7 @@ import type { TopologyDescriptor } from '../topology/types.js'
|
|
|
21
22
|
* OPTIONAL and says what its product should do for the people using it. Optional because a
|
|
22
23
|
* blueprint registered before the layer existed — or a patch serialized into a run row — must keep
|
|
23
24
|
* resolving: every reader asks through a helper that answers the default for an absent layer
|
|
24
|
-
* ({@link landingGatePreferenceOf}).
|
|
25
|
+
* ({@link landingGatePreferenceOf}, `tenancyOf`).
|
|
25
26
|
*/
|
|
26
27
|
export interface Blueprint {
|
|
27
28
|
id: string
|
|
@@ -85,6 +86,14 @@ export enum LandingGatePreference {
|
|
|
85
86
|
*/
|
|
86
87
|
export interface ExperienceLayer {
|
|
87
88
|
landingGate: LandingGatePreference
|
|
89
|
+
/**
|
|
90
|
+
* Whether the product splits its audiences into tenant organizations.
|
|
91
|
+
*
|
|
92
|
+
* Written by resolution from {@link BlueprintRef.tenancy} — the project's own decision — and by
|
|
93
|
+
* no blueprint or case table, which cannot know it. Absent is a single-organization application;
|
|
94
|
+
* always read through `tenancyOf`.
|
|
95
|
+
*/
|
|
96
|
+
tenancy?: ProjectTenancy
|
|
88
97
|
}
|
|
89
98
|
|
|
90
99
|
const LANDING_GATE_PREFERENCES = new Set<string>(Object.values(LandingGatePreference))
|
|
@@ -266,5 +275,14 @@ export interface BlueprintRef {
|
|
|
266
275
|
* which is the only reading that cannot silently take a capability away from a live project.
|
|
267
276
|
*/
|
|
268
277
|
case?: string
|
|
278
|
+
/**
|
|
279
|
+
* Whether the product splits its audiences into tenant organizations — the project card's
|
|
280
|
+
* decision, resolved into `experience.tenancy`.
|
|
281
|
+
*
|
|
282
|
+
* Flags rather than a key, because the decision is the project's own and no table could hold it;
|
|
283
|
+
* two booleans are still no artifact. Absent, or both false, applies nothing, so a run row of a
|
|
284
|
+
* project nobody decided for serializes exactly as it did before tenancy existed.
|
|
285
|
+
*/
|
|
286
|
+
tenancy?: ProjectTenancy
|
|
269
287
|
overrides?: BlueprintPatch
|
|
270
288
|
}
|
package/src/connect/consts.ts
CHANGED
|
@@ -260,9 +260,9 @@ export const CONNECT_INQUIRY_MAX_TEXT = 2_000
|
|
|
260
260
|
/**
|
|
261
261
|
* How long a session survives without a sign of life.
|
|
262
262
|
*
|
|
263
|
-
* A connector refreshes its presence on every
|
|
264
|
-
*
|
|
265
|
-
*
|
|
263
|
+
* A connector refreshes its presence on every long poll and every submit. Ten minutes is generous
|
|
264
|
+
* on purpose: a parent agent running one model task in a subagent can be silent for several, and
|
|
265
|
+
* expiring a session under it would fail a run that was progressing.
|
|
266
266
|
*/
|
|
267
267
|
export const CONNECT_SESSION_EXPIRE_MS = 600_000
|
|
268
268
|
|
|
@@ -354,10 +354,6 @@ export const CONNECT_MARKER_DIR = '.viable'
|
|
|
354
354
|
export const CONNECT_ENV_BEGIN = '# --- viable:managed ---'
|
|
355
355
|
export const CONNECT_ENV_END = '# --- /viable:managed ---'
|
|
356
356
|
|
|
357
|
-
/** Socket events a connector session carries. */
|
|
358
|
-
export const CONNECT_EVENT_OP = 'connect:op'
|
|
359
|
-
export const CONNECT_EVENT_SESSION = 'connect:session'
|
|
360
|
-
|
|
361
357
|
/** Redis resource aliases the platform registers for the relay. */
|
|
362
358
|
export const CONNECT_OP_PUBSUB = 'connect-op-pubsub'
|
|
363
359
|
export const CONNECT_RESULT_PUBSUB = 'connect-result-pubsub'
|
|
@@ -381,14 +377,10 @@ export const RES_CONNECT_SESSION = 'connect-session'
|
|
|
381
377
|
*/
|
|
382
378
|
export const connect = Object.freeze({
|
|
383
379
|
base: 'viable:manager-api:connect:base',
|
|
384
|
-
capabilities: 'viable:manager-api:connect:capabilities',
|
|
385
380
|
session: Object.freeze({
|
|
386
381
|
open: 'viable:manager-api:connect:session:open',
|
|
387
382
|
openDelegated: 'viable:manager-api:connect:session:open-delegated',
|
|
388
|
-
get: 'viable:manager-api:connect:session:get',
|
|
389
|
-
heartbeat: 'viable:manager-api:connect:session:heartbeat',
|
|
390
383
|
close: 'viable:manager-api:connect:session:close',
|
|
391
|
-
socket: 'viable:manager-api:connect:session:socket',
|
|
392
384
|
}),
|
|
393
385
|
op: Object.freeze({
|
|
394
386
|
pull: 'viable:manager-api:connect:op:pull',
|
|
@@ -402,10 +394,6 @@ export const connect = Object.freeze({
|
|
|
402
394
|
attach: 'viable:manager-api:connect:project:attach',
|
|
403
395
|
reinit: 'viable:manager-api:connect:project:reinit',
|
|
404
396
|
modify: 'viable:manager-api:connect:project:modify',
|
|
405
|
-
settings: 'viable:manager-api:connect:project:settings',
|
|
406
|
-
llm: 'viable:manager-api:connect:project:llm',
|
|
407
|
-
/** The per-project converter inference mode. Separate from `llm`: it is not a paid capability. */
|
|
408
|
-
converterLlm: 'viable:manager-api:connect:project:converter-llm',
|
|
409
397
|
/**
|
|
410
398
|
* The project's own branding — copyright, organization, the two legal links, the Google tag.
|
|
411
399
|
* The platform credit is deliberately NOT here: hiding it is a paid capability with its own
|
|
@@ -424,7 +412,6 @@ export const connect = Object.freeze({
|
|
|
424
412
|
check: 'viable:manager-api:connect:convert:check',
|
|
425
413
|
start: 'viable:manager-api:connect:convert:start',
|
|
426
414
|
proceed: 'viable:manager-api:connect:convert:proceed',
|
|
427
|
-
cancel: 'viable:manager-api:connect:convert:cancel',
|
|
428
415
|
status: 'viable:manager-api:connect:convert:status',
|
|
429
416
|
purge: 'viable:manager-api:connect:convert:purge',
|
|
430
417
|
}),
|
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
import { contract, openProtocol, protocol, typed } from '@owlmeans/entrypoint'
|
|
2
|
-
import
|
|
3
|
-
import { route, RouteMethod, socket } from '@owlmeans/route'
|
|
4
|
-
import { ConverterProjectLlmBodySchema } from '../convert/schemas.js'
|
|
2
|
+
import { route, RouteMethod } from '@owlmeans/route'
|
|
5
3
|
import { connect } from './consts.js'
|
|
6
4
|
import {
|
|
7
5
|
ConnectAttachBodySchema, ConnectConfirmBodySchema, ConnectConvertCreateBodySchema,
|
|
8
6
|
ConnectConvertProceedBodySchema, ConnectCreateBodySchema, ConnectInquiryParamsSchema,
|
|
9
7
|
ConnectModifyBodySchema, ConnectOpParamsSchema, ConnectOpResultSchema,
|
|
10
8
|
ConnectPipelineParamsSchema, ConnectPipelineResumeBodySchema, ConnectProjectBrandingSaveSchema,
|
|
11
|
-
ConnectProjectIdSchema,
|
|
12
|
-
|
|
9
|
+
ConnectProjectIdSchema, ConnectSessionOpenSchema, ConnectSessionParamsSchema, ConnectPullQuerySchema,
|
|
10
|
+
ConnectStoryParamsSchema, InquiryAnswerSchema,
|
|
13
11
|
} from './schemas.js'
|
|
14
12
|
import type {
|
|
15
13
|
ConnectAttachBody, ConnectConfirmBody, ConnectConvertCreateBody, ConnectConvertProceedBody,
|
|
16
14
|
ConnectCreateBody, ConnectInquiryAnswerBody, ConnectModifyBody,
|
|
17
15
|
ConnectPipelineParams, ConnectPipelineResumeBody, ConnectProjectBranding,
|
|
18
|
-
ConnectProjectBrandingSave,
|
|
16
|
+
ConnectProjectBrandingSave, ConnectSessionOpen,
|
|
19
17
|
ConnectPipelineState, ConnectProjectStatus, ConnectPullQuery, ConnectSessionParams,
|
|
20
18
|
ConnectStoryStatus, ConversionStatusView, ConvertCheck,
|
|
21
19
|
} from './types.js'
|
|
22
20
|
import type { ConnectOpResult } from './ops.js'
|
|
23
|
-
import type { ConverterProjectLlmBody } from '../convert/types.js'
|
|
24
21
|
|
|
25
22
|
/**
|
|
26
23
|
* What the platform injects when it mounts the connector routes.
|
|
@@ -37,19 +34,19 @@ export interface ConnectEntrypointOptions {
|
|
|
37
34
|
/**
|
|
38
35
|
* The gate that decides whether the caller may use the local-LLM mode.
|
|
39
36
|
*
|
|
40
|
-
* Applied to the
|
|
41
|
-
*
|
|
42
|
-
*
|
|
37
|
+
* Applied to the one route that turns it on — opening a delegated session. Everything else is
|
|
38
|
+
* free: `cloud` is the default, and a project's own override is the platform's browser surface,
|
|
39
|
+
* not the connector's.
|
|
43
40
|
*/
|
|
44
41
|
localLlm?: { alias: string, params: string[] }
|
|
45
|
-
/** The platform-owned websocket-base protocol that carries session updates. */
|
|
46
|
-
updateBase: EntrypointProtocol<OpenRequest, OpenValue>
|
|
47
42
|
/** Path prefix; defaults to `/connect`. */
|
|
48
43
|
path?: string
|
|
49
44
|
}
|
|
50
45
|
|
|
51
46
|
/**
|
|
52
|
-
* Declare the connector's HTTP
|
|
47
|
+
* Declare the connector's HTTP surface — exactly the routes a connector calls: the session (long
|
|
48
|
+
* poll, no socket), the operation relay, projects, story status, generated files, conversion,
|
|
49
|
+
* inquiry answers and pipeline state.
|
|
53
50
|
*
|
|
54
51
|
* One immutable tree, mounted directly by the platform's entrypoint tree. Handlers are bound to these protocols
|
|
55
52
|
* server-side and onto client entrypoints in the SDK — the same declarations both times, which is
|
|
@@ -69,11 +66,6 @@ export const connectProtocols = (opts: ConnectEntrypointOptions) => {
|
|
|
69
66
|
return {
|
|
70
67
|
base,
|
|
71
68
|
|
|
72
|
-
capabilities: protocol(
|
|
73
|
-
route(connect.capabilities, '/capabilities', { parent: base, method: RouteMethod.GET }),
|
|
74
|
-
contract(typed()),
|
|
75
|
-
),
|
|
76
|
-
|
|
77
69
|
// --- session ---------------------------------------------------------------------------
|
|
78
70
|
session: {
|
|
79
71
|
open: protocol(
|
|
@@ -86,29 +78,12 @@ export const connectProtocols = (opts: ConnectEntrypointOptions) => {
|
|
|
86
78
|
}),
|
|
87
79
|
contract.request({ body: typed<ConnectSessionOpen>(ConnectSessionOpenSchema) }, typed()), paid
|
|
88
80
|
),
|
|
89
|
-
get: protocol(
|
|
90
|
-
route(connect.session.get, '/session/:sessionId', {
|
|
91
|
-
parent: base, method: RouteMethod.GET
|
|
92
|
-
}),
|
|
93
|
-
contract.request({ params: typed<ConnectSessionParams>(ConnectSessionParamsSchema) }, typed())
|
|
94
|
-
),
|
|
95
|
-
heartbeat: protocol(
|
|
96
|
-
route(connect.session.heartbeat, '/session/:sessionId/heartbeat', {
|
|
97
|
-
parent: base, method: RouteMethod.POST
|
|
98
|
-
}),
|
|
99
|
-
contract.request({ params: typed<ConnectSessionParams>(ConnectSessionParamsSchema) }, typed())
|
|
100
|
-
),
|
|
101
81
|
close: protocol(
|
|
102
82
|
route(connect.session.close, '/session/:sessionId/close', {
|
|
103
83
|
parent: base, method: RouteMethod.POST
|
|
104
84
|
}),
|
|
105
85
|
contract.request({ params: typed<ConnectSessionParams>(ConnectSessionParamsSchema) }, typed())
|
|
106
86
|
),
|
|
107
|
-
socket: protocol(
|
|
108
|
-
route(connect.session.socket, '/connect/:sessionId', socket({ parent: opts.updateBase })),
|
|
109
|
-
contract.request({ params: typed<ConnectSessionParams>(ConnectSessionParamsSchema) }, typed())
|
|
110
|
-
),
|
|
111
|
-
|
|
112
87
|
},
|
|
113
88
|
|
|
114
89
|
// --- operations ------------------------------------------------------------------------
|
|
@@ -168,27 +143,6 @@ export const connectProtocols = (opts: ConnectEntrypointOptions) => {
|
|
|
168
143
|
}),
|
|
169
144
|
contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema), body: typed<ConnectModifyBody>(ConnectModifyBodySchema) }, typed<ConnectProjectStatus>())
|
|
170
145
|
),
|
|
171
|
-
settings: protocol(
|
|
172
|
-
route(connect.project.settings, '/project/:id/settings', {
|
|
173
|
-
parent: base, method: RouteMethod.GET
|
|
174
|
-
}),
|
|
175
|
-
contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed())
|
|
176
|
-
),
|
|
177
|
-
llm: protocol(
|
|
178
|
-
route(connect.project.llm, '/project/:id/llm', {
|
|
179
|
-
parent: base, method: RouteMethod.POST
|
|
180
|
-
}),
|
|
181
|
-
contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema), body: typed<ConnectProjectLlmBody>(ConnectProjectLlmBodySchema) }, typed()), paid
|
|
182
|
-
),
|
|
183
|
-
converterLlm: protocol(
|
|
184
|
-
route(connect.project.converterLlm, '/project/:id/converter-llm', {
|
|
185
|
-
parent: base, method: RouteMethod.POST,
|
|
186
|
-
}),
|
|
187
|
-
contract.request({
|
|
188
|
-
params: typed<{ id: string }>(ConnectProjectIdSchema),
|
|
189
|
-
body: typed<ConverterProjectLlmBody>(ConverterProjectLlmBodySchema),
|
|
190
|
-
}, typed()),
|
|
191
|
-
),
|
|
192
146
|
// Under `base` like every sibling — the guard and the ownership gate — and never under the
|
|
193
147
|
// paid gate: saving branding is free, and the paid credit switch is not reachable from here.
|
|
194
148
|
branding: {
|
|
@@ -252,10 +206,6 @@ export const connectProtocols = (opts: ConnectEntrypointOptions) => {
|
|
|
252
206
|
body: typed<ConnectConvertProceedBody>(ConnectConvertProceedBodySchema),
|
|
253
207
|
}, typed<ConversionStatusView>()),
|
|
254
208
|
),
|
|
255
|
-
cancel: protocol(
|
|
256
|
-
route(connect.convert.cancel, '/convert/:id/cancel', { parent: base, method: RouteMethod.POST }),
|
|
257
|
-
contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConversionStatusView>()),
|
|
258
|
-
),
|
|
259
209
|
status: protocol(
|
|
260
210
|
route(connect.convert.status, '/convert/:id', { parent: base, method: RouteMethod.GET }),
|
|
261
211
|
contract.request({ params: typed<{ id: string }>(ConnectProjectIdSchema) }, typed<ConversionStatusView>()),
|
|
@@ -3,13 +3,11 @@ import type { RegisteredEntrypoint, RequestShape } from '@owlmeans/entrypoint'
|
|
|
3
3
|
import type { EntrypointReference } from '@owlmeans/context'
|
|
4
4
|
import { connect } from './consts.js'
|
|
5
5
|
import type { ConnectOp, ConnectOpResult } from './ops.js'
|
|
6
|
-
import type { ConverterProjectLlmBody } from '../convert/index.js'
|
|
7
6
|
import type {
|
|
8
|
-
ConnectAttachBody,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
ConnectProjectSettings, ConnectProjectStatus, ConnectProjectSummary, ConnectPullQuery,
|
|
7
|
+
ConnectAttachBody, ConnectConfirmBody, ConnectCreateBody, ConnectConvertCreateBody,
|
|
8
|
+
ConnectConvertProceedBody, ConnectInquiryAnswerBody, ConnectModifyBody, ConnectOpSubmission,
|
|
9
|
+
ConnectPipelineParams, ConnectPipelineResumeBody, ConnectPipelineState, ConnectProjectBranding,
|
|
10
|
+
ConnectProjectBrandingSave, ConnectProjectStatus, ConnectProjectSummary, ConnectPullQuery,
|
|
13
11
|
ConnectSessionOpen, ConnectSessionParams, ConnectSessionView, ConnectStoryStatus,
|
|
14
12
|
ConversionStatusView, ConvertCheck,
|
|
15
13
|
} from './types.js'
|
|
@@ -18,12 +16,9 @@ type ConnectReference<Request extends RequestShape, Response> =
|
|
|
18
16
|
EntrypointReference<RegisteredEntrypoint<Request, Response>>
|
|
19
17
|
|
|
20
18
|
export interface ConnectReferences {
|
|
21
|
-
capabilities: ConnectReference<{}, ConnectCapabilitiesView>
|
|
22
19
|
session: {
|
|
23
20
|
open: ConnectReference<{ body: ConnectSessionOpen }, ConnectSessionView>
|
|
24
21
|
openDelegated: ConnectReference<{ body: ConnectSessionOpen }, ConnectSessionView>
|
|
25
|
-
get: ConnectReference<{ params: ConnectSessionParams }, ConnectSessionView>
|
|
26
|
-
heartbeat: ConnectReference<{ params: ConnectSessionParams }, ConnectSessionView>
|
|
27
22
|
close: ConnectReference<{ params: ConnectSessionParams }, ConnectSessionView>
|
|
28
23
|
}
|
|
29
24
|
op: {
|
|
@@ -40,11 +35,6 @@ export interface ConnectReferences {
|
|
|
40
35
|
attach: ConnectReference<{ body: ConnectAttachBody }, ConnectProjectStatus>
|
|
41
36
|
reinit: ConnectReference<{ params: { id: string } }, ConnectProjectStatus>
|
|
42
37
|
modify: ConnectReference<{ params: { id: string }, body: ConnectModifyBody }, ConnectProjectStatus>
|
|
43
|
-
settings: ConnectReference<{ params: { id: string } }, ConnectProjectSettings>
|
|
44
|
-
llm: ConnectReference<{ params: { id: string }, body: ConnectProjectLlmBody }, ConnectProjectSettings>
|
|
45
|
-
converterLlm: ConnectReference<{
|
|
46
|
-
params: { id: string }, body: ConverterProjectLlmBody
|
|
47
|
-
}, ConnectProjectSettings>
|
|
48
38
|
branding: {
|
|
49
39
|
get: ConnectReference<{ params: { id: string } }, ConnectProjectBranding>
|
|
50
40
|
save: ConnectReference<{
|
|
@@ -65,7 +55,6 @@ export interface ConnectReferences {
|
|
|
65
55
|
proceed: ConnectReference<{
|
|
66
56
|
params: { id: string }, body: ConnectConvertProceedBody
|
|
67
57
|
}, ConversionStatusView>
|
|
68
|
-
cancel: ConnectReference<{ params: { id: string } }, ConversionStatusView>
|
|
69
58
|
status: ConnectReference<{ params: { id: string } }, ConversionStatusView>
|
|
70
59
|
purge: ConnectReference<{ params: { id: string } }, ConversionStatusView>
|
|
71
60
|
}
|
|
@@ -88,12 +77,9 @@ export interface ConnectReferences {
|
|
|
88
77
|
* aliases to UI or domain code.
|
|
89
78
|
*/
|
|
90
79
|
export const connectRef: ConnectReferences = {
|
|
91
|
-
capabilities: entrypointRef<{}, ConnectCapabilitiesView>(connect.capabilities),
|
|
92
80
|
session: {
|
|
93
81
|
open: entrypointRef<{ body: ConnectSessionOpen }, ConnectSessionView>(connect.session.open),
|
|
94
82
|
openDelegated: entrypointRef<{ body: ConnectSessionOpen }, ConnectSessionView>(connect.session.openDelegated),
|
|
95
|
-
get: entrypointRef<{ params: ConnectSessionParams }, ConnectSessionView>(connect.session.get),
|
|
96
|
-
heartbeat: entrypointRef<{ params: ConnectSessionParams }, ConnectSessionView>(connect.session.heartbeat),
|
|
97
83
|
close: entrypointRef<{ params: ConnectSessionParams }, ConnectSessionView>(connect.session.close),
|
|
98
84
|
},
|
|
99
85
|
op: {
|
|
@@ -110,13 +96,6 @@ export const connectRef: ConnectReferences = {
|
|
|
110
96
|
attach: entrypointRef<{ body: ConnectAttachBody }, ConnectProjectStatus>(connect.project.attach),
|
|
111
97
|
reinit: entrypointRef<{ params: { id: string } }, ConnectProjectStatus>(connect.project.reinit),
|
|
112
98
|
modify: entrypointRef<{ params: { id: string }, body: ConnectModifyBody }, ConnectProjectStatus>(connect.project.modify),
|
|
113
|
-
settings: entrypointRef<{ params: { id: string } }, ConnectProjectSettings>(connect.project.settings),
|
|
114
|
-
llm: entrypointRef<{
|
|
115
|
-
params: { id: string }, body: ConnectProjectLlmBody
|
|
116
|
-
}, ConnectProjectSettings>(connect.project.llm),
|
|
117
|
-
converterLlm: entrypointRef<{
|
|
118
|
-
params: { id: string }, body: ConverterProjectLlmBody
|
|
119
|
-
}, ConnectProjectSettings>(connect.project.converterLlm),
|
|
120
99
|
branding: {
|
|
121
100
|
get: entrypointRef<{ params: { id: string } }, ConnectProjectBranding>(connect.project.branding.get),
|
|
122
101
|
save: entrypointRef<{
|
|
@@ -136,7 +115,6 @@ export const connectRef: ConnectReferences = {
|
|
|
136
115
|
proceed: entrypointRef<{
|
|
137
116
|
params: { id: string }, body: ConnectConvertProceedBody
|
|
138
117
|
}, ConversionStatusView>(connect.convert.proceed),
|
|
139
|
-
cancel: entrypointRef<{ params: { id: string } }, ConversionStatusView>(connect.convert.cancel),
|
|
140
118
|
status: entrypointRef<{
|
|
141
119
|
params: { id: string }
|
|
142
120
|
}, ConversionStatusView>(connect.convert.status),
|
package/src/connect/types.ts
CHANGED
|
@@ -186,8 +186,7 @@ export interface ConnectProfileSettingsView extends ConnectProfileSettings {
|
|
|
186
186
|
* work: a conversion reads somebody else's whole repository, which is the one case where handing
|
|
187
187
|
* the inference to the parent agent is the cheap default rather than the experimental option.
|
|
188
188
|
*
|
|
189
|
-
* OPTIONAL
|
|
190
|
-
* optionally: the two converter fields are answered by a platform build that does not exist yet,
|
|
189
|
+
* OPTIONAL: the two converter fields are answered by a platform build that does not exist yet,
|
|
191
190
|
* and this package is consumed by the platform through a workspace link rather than a published
|
|
192
191
|
* range — a required field here is a compile error in every handler that already returns this
|
|
193
192
|
* view. Tightened to required once the platform's conversion handlers fill them.
|
|
@@ -251,26 +250,6 @@ export interface ConnectProjectBranding {
|
|
|
251
250
|
*/
|
|
252
251
|
export interface ConnectProjectBrandingSave extends Partial<ConnectProjectBranding> {}
|
|
253
252
|
|
|
254
|
-
/** What the platform tells a connector about itself. */
|
|
255
|
-
export interface ConnectCapabilitiesView {
|
|
256
|
-
/** Tier → the platform role names that will be asked for at that tier. */
|
|
257
|
-
tiers: Record<ModelTier, string[]>
|
|
258
|
-
/** Whether this caller may open a local-LLM session. */
|
|
259
|
-
localLlm: boolean
|
|
260
|
-
/**
|
|
261
|
-
* The default mode for a new session, resolved from the caller's settings.
|
|
262
|
-
*
|
|
263
|
-
* `converterLlm` is OPTIONAL: an older platform does not send it, and a client that treated its
|
|
264
|
-
* absence as a value would pin every conversion to whatever its own default happened to be.
|
|
265
|
-
*/
|
|
266
|
-
defaults: { target: ConnectTarget, llm: ConnectLlm, converterLlm?: ConnectLlm }
|
|
267
|
-
limits: {
|
|
268
|
-
pullWaitMs: number
|
|
269
|
-
modelTaskTimeoutMs: number
|
|
270
|
-
maxPending: number
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
253
|
/** Attach an existing project to a session. */
|
|
275
254
|
export interface ConnectAttachBody {
|
|
276
255
|
projectId?: string
|
package/src/dev/consts.ts
CHANGED
|
@@ -10,6 +10,22 @@ export enum AccessLevel {
|
|
|
10
10
|
Admin = 'admin',
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Who holds a permission without anybody granting it — the default CLASS of its definition.
|
|
15
|
+
*
|
|
16
|
+
* Evaluated by the IAM when it issues claims, never copied onto rows, so a person who joins an
|
|
17
|
+
* organization later holds what its members hold with nothing written for them. `User` is every
|
|
18
|
+
* signed-in person of the application; `Member` and `Owner` are the members and the owners of the
|
|
19
|
+
* organization being acted in, and therefore mean anything only for an organization-bound
|
|
20
|
+
* permission. `None` is the ordinary permission: held only where it was granted.
|
|
21
|
+
*/
|
|
22
|
+
export enum PermissionDefault {
|
|
23
|
+
None = 'none',
|
|
24
|
+
User = 'user',
|
|
25
|
+
Member = 'member',
|
|
26
|
+
Owner = 'owner',
|
|
27
|
+
}
|
|
28
|
+
|
|
13
29
|
/**
|
|
14
30
|
* Alias prefixes of the entrypoints a generated project declares.
|
|
15
31
|
*
|
package/src/dev/schemas.ts
CHANGED
|
@@ -80,7 +80,10 @@ export const EntrypointRefListSchema: JSONSchemaType<{entries: EntrypointRef[]}>
|
|
|
80
80
|
additionalProperties: false
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
/** The part of an {@link AccessBlock} a model is asked for. */
|
|
84
|
+
type ModelAccessBlock = Omit<AccessBlock, 'defaults' | 'entityScoped'>
|
|
85
|
+
|
|
86
|
+
const ModelAccessBlockSchema: JSONSchemaType<ModelAccessBlock> = {
|
|
84
87
|
type: "object",
|
|
85
88
|
title: "AccessBlock",
|
|
86
89
|
description: "Object with permissions array and access level",
|
|
@@ -119,6 +122,15 @@ export const AccessBlockSchema: JSONSchemaType<AccessBlock> = {
|
|
|
119
122
|
additionalProperties: false
|
|
120
123
|
}
|
|
121
124
|
|
|
125
|
+
/**
|
|
126
|
+
* The access block a model writes.
|
|
127
|
+
*
|
|
128
|
+
* It names only what a model is asked for: `defaults` and `entityScoped` are written by code and
|
|
129
|
+
* must never reach a prompt, which `tests/access-schema.spec.ts` pins byte for byte. A value it
|
|
130
|
+
* admits is still an {@link AccessBlock}, because both code-written keys are optional.
|
|
131
|
+
*/
|
|
132
|
+
export const AccessBlockSchema = ModelAccessBlockSchema as unknown as JSONSchemaType<AccessBlock>
|
|
133
|
+
|
|
122
134
|
export const AccessListSchema: JSONSchemaType<AccessList> = {
|
|
123
135
|
type: "object",
|
|
124
136
|
title: "AccessList",
|
package/src/dev/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AccessLevel } from "./consts.js"
|
|
1
|
+
import type { AccessLevel, PermissionDefault } from "./consts.js"
|
|
2
2
|
|
|
3
3
|
export interface SourceFile {
|
|
4
4
|
path: string
|
|
@@ -33,6 +33,20 @@ export interface AccessBlock {
|
|
|
33
33
|
level: AccessLevel
|
|
34
34
|
/** Gate params safe for every newly signed-in user; a subset of permissions. */
|
|
35
35
|
defaultEnabledPermissions?: string[]
|
|
36
|
+
/**
|
|
37
|
+
* The default class of each permission this block names, keyed by the gate param with its
|
|
38
|
+
* `@<routeParam>` selector stripped — the name the definition is registered under.
|
|
39
|
+
*
|
|
40
|
+
* Written by CODE, never by a model: `AccessBlockSchema` does not carry it, so a model answer
|
|
41
|
+
* never holds one, and what decides it is the project's tenancy and the area, not a reading of
|
|
42
|
+
* the story.
|
|
43
|
+
*/
|
|
44
|
+
defaults?: Record<string, PermissionDefault>
|
|
45
|
+
/**
|
|
46
|
+
* Whether the permissions of this block are bound to the organization being acted in, rather than
|
|
47
|
+
* held everywhere. Written by code, like {@link AccessBlock.defaults}.
|
|
48
|
+
*/
|
|
49
|
+
entityScoped?: boolean
|
|
36
50
|
}
|
|
37
51
|
|
|
38
52
|
/**
|
package/src/planning/schemas.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { JSONSchemaType } from 'ajv'
|
|
2
2
|
import { CODE_MAX } from '@owlmeans/planning'
|
|
3
3
|
import { ProjectArea } from '../areas/consts.js'
|
|
4
|
+
import { TENANCY_QUOTE_MAX } from '../areas/tenancy.js'
|
|
4
5
|
import { StoryKind } from '../ba/consts.js'
|
|
5
6
|
import { ConnectLlm, ConnectTarget } from '../connect/consts.js'
|
|
6
7
|
import { ProjectOriginSchema } from '../convert/schemas.js'
|
|
@@ -40,6 +41,30 @@ export const ViableProjectFieldsSchema = {
|
|
|
40
41
|
required: ['at'],
|
|
41
42
|
additionalProperties: false,
|
|
42
43
|
},
|
|
44
|
+
// An absent `tenancy` is "never decided"; a recorded one always carries both flags, so a
|
|
45
|
+
// reader never has to guess what a missing flag meant.
|
|
46
|
+
tenancy: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
nullable: true,
|
|
49
|
+
properties: {
|
|
50
|
+
operators: { type: 'boolean' },
|
|
51
|
+
users: { type: 'boolean' },
|
|
52
|
+
quotes: {
|
|
53
|
+
type: 'object',
|
|
54
|
+
nullable: true,
|
|
55
|
+
properties: {
|
|
56
|
+
operators: { type: 'string', minLength: 1, maxLength: TENANCY_QUOTE_MAX, nullable: true },
|
|
57
|
+
users: { type: 'string', minLength: 1, maxLength: TENANCY_QUOTE_MAX, nullable: true },
|
|
58
|
+
},
|
|
59
|
+
required: [],
|
|
60
|
+
additionalProperties: false,
|
|
61
|
+
},
|
|
62
|
+
by: { type: 'string', enum: ['model', 'owner'] },
|
|
63
|
+
at: { type: 'string', minLength: 1, maxLength: 32 },
|
|
64
|
+
},
|
|
65
|
+
required: ['operators', 'users', 'by', 'at'],
|
|
66
|
+
additionalProperties: false,
|
|
67
|
+
},
|
|
43
68
|
},
|
|
44
69
|
required: [],
|
|
45
70
|
additionalProperties: false,
|
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 { ViableTenancyDecision } from '../areas/tenancy.js'
|
|
3
4
|
import type { StoryKind } from '../ba/consts.js'
|
|
4
5
|
import type { ConnectLlm, ConnectTarget } from '../connect/consts.js'
|
|
5
6
|
import type { ProjectOrigin } from '../convert/types.js'
|
|
@@ -42,6 +43,14 @@ export type ViableProjectFields = {
|
|
|
42
43
|
* "none" and no later run would ask again. `at` is when it was decided, an ISO timestamp.
|
|
43
44
|
*/
|
|
44
45
|
landing?: ViableLandingDecision
|
|
46
|
+
/**
|
|
47
|
+
* Whether the target splits its operators and its end users into tenant organizations.
|
|
48
|
+
*
|
|
49
|
+
* ABSENT means never decided and reads as a single-organization application, exactly as every
|
|
50
|
+
* project before tenancy. It reaches a run through the blueprint reference (`BlueprintRef.tenancy`)
|
|
51
|
+
* only when a flag is on.
|
|
52
|
+
*/
|
|
53
|
+
tenancy?: ViableTenancyDecision
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
/** The recorded landing-gate decision — see {@link ViableProjectFields.landing}. */
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { createHash } from 'node:crypto'
|
|
3
|
+
import { AccessBlockSchema, AccessLevel, AccessListSchema, PermissionDefault } from '../src/index.js'
|
|
4
|
+
import type { AccessBlock } from '../src/index.js'
|
|
5
|
+
|
|
6
|
+
const sha256 = (value: unknown): string =>
|
|
7
|
+
createHash('sha256').update(JSON.stringify(value)).digest('hex')
|
|
8
|
+
|
|
9
|
+
// The model-facing schemas, byte for byte. They are rendered into the access prompts verbatim and
|
|
10
|
+
// handed to the model as its answer contract, so a change here changes what every model is asked —
|
|
11
|
+
// update these only together with the prompts that read them.
|
|
12
|
+
const ACCESS_BLOCK_SHA256 = '577c8406a73604128ff016a35fe918960d1316a1a24fe4737be31e41478c6ca8'
|
|
13
|
+
const ACCESS_LIST_SHA256 = '80b3ab836718ce69c97e9854f647f2b5ed794ec4b871e86ae94b0fad4bf68a50'
|
|
14
|
+
|
|
15
|
+
describe('viable-common - the access schema a model is asked for', () => {
|
|
16
|
+
test('names only the model-written keys, never the code-written defaults or binding', () => {
|
|
17
|
+
expect(Object.keys(AccessBlockSchema.properties ?? {})).toEqual(
|
|
18
|
+
['permissions', 'level', 'defaultEnabledPermissions']
|
|
19
|
+
)
|
|
20
|
+
expect(JSON.stringify(AccessListSchema)).not.toContain('entityScoped')
|
|
21
|
+
expect(JSON.stringify(AccessListSchema)).not.toContain('"defaults"')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('is byte-identical to the schema the access prompts were written against', () => {
|
|
25
|
+
expect(sha256(AccessBlockSchema)).toBe(ACCESS_BLOCK_SHA256)
|
|
26
|
+
expect(sha256(AccessListSchema)).toBe(ACCESS_LIST_SHA256)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('an access block still carries the code-written keys as data', () => {
|
|
30
|
+
const block: AccessBlock = {
|
|
31
|
+
permissions: ['enquiry--view@enquiryId'], level: AccessLevel.Permissioned,
|
|
32
|
+
defaults: { 'enquiry--view': PermissionDefault.Member }, entityScoped: true,
|
|
33
|
+
}
|
|
34
|
+
expect(block.defaults).toEqual({ 'enquiry--view': 'member' })
|
|
35
|
+
expect(Object.values(PermissionDefault)).toEqual(['none', 'user', 'member', 'owner'])
|
|
36
|
+
})
|
|
37
|
+
})
|