@softacus-software/elwis-mcp-core 0.1.0
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 +120 -0
- package/dist/cjs/app-scope.js +81 -0
- package/dist/cjs/app-scope.js.map +1 -0
- package/dist/cjs/conformance/host.conformance.js +98 -0
- package/dist/cjs/conformance/host.conformance.js.map +1 -0
- package/dist/cjs/conformance/read-port.conformance.js +131 -0
- package/dist/cjs/conformance/read-port.conformance.js.map +1 -0
- package/dist/cjs/errors.js +74 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/host/elwis-jwt-verifier.js +40 -0
- package/dist/cjs/host/elwis-jwt-verifier.js.map +1 -0
- package/dist/cjs/host/file-link-store.js +60 -0
- package/dist/cjs/host/file-link-store.js.map +1 -0
- package/dist/cjs/host/identity.js +3 -0
- package/dist/cjs/host/identity.js.map +1 -0
- package/dist/cjs/host/index.js +24 -0
- package/dist/cjs/host/index.js.map +1 -0
- package/dist/cjs/host/link-ticket.js +59 -0
- package/dist/cjs/host/link-ticket.js.map +1 -0
- package/dist/cjs/host/oidc-token.js +96 -0
- package/dist/cjs/host/oidc-token.js.map +1 -0
- package/dist/cjs/host/oidc-verifier.js +67 -0
- package/dist/cjs/host/oidc-verifier.js.map +1 -0
- package/dist/cjs/host/serve-http.js +282 -0
- package/dist/cjs/host/serve-http.js.map +1 -0
- package/dist/cjs/host/session-broker.js +84 -0
- package/dist/cjs/host/session-broker.js.map +1 -0
- package/dist/cjs/index.js +43 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/plan/expand.js +297 -0
- package/dist/cjs/plan/expand.js.map +1 -0
- package/dist/cjs/plan/resolve-existing.js +58 -0
- package/dist/cjs/plan/resolve-existing.js.map +1 -0
- package/dist/cjs/plan/split.js +92 -0
- package/dist/cjs/plan/split.js.map +1 -0
- package/dist/cjs/plan/target-state.js +120 -0
- package/dist/cjs/plan/target-state.js.map +1 -0
- package/dist/cjs/ports/elwis-api.port.js +15 -0
- package/dist/cjs/ports/elwis-api.port.js.map +1 -0
- package/dist/cjs/resolve.js +89 -0
- package/dist/cjs/resolve.js.map +1 -0
- package/dist/esm/app-scope.d.ts +26 -0
- package/dist/esm/app-scope.d.ts.map +1 -0
- package/dist/esm/app-scope.js +76 -0
- package/dist/esm/app-scope.js.map +1 -0
- package/dist/esm/conformance/host.conformance.d.ts +3 -0
- package/dist/esm/conformance/host.conformance.d.ts.map +1 -0
- package/dist/esm/conformance/host.conformance.js +95 -0
- package/dist/esm/conformance/host.conformance.js.map +1 -0
- package/dist/esm/conformance/read-port.conformance.d.ts +24 -0
- package/dist/esm/conformance/read-port.conformance.d.ts.map +1 -0
- package/dist/esm/conformance/read-port.conformance.js +127 -0
- package/dist/esm/conformance/read-port.conformance.js.map +1 -0
- package/dist/esm/errors.d.ts +40 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +63 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/host/elwis-jwt-verifier.d.ts +9 -0
- package/dist/esm/host/elwis-jwt-verifier.d.ts.map +1 -0
- package/dist/esm/host/elwis-jwt-verifier.js +37 -0
- package/dist/esm/host/elwis-jwt-verifier.js.map +1 -0
- package/dist/esm/host/file-link-store.d.ts +13 -0
- package/dist/esm/host/file-link-store.d.ts.map +1 -0
- package/dist/esm/host/file-link-store.js +56 -0
- package/dist/esm/host/file-link-store.js.map +1 -0
- package/dist/esm/host/identity.d.ts +13 -0
- package/dist/esm/host/identity.d.ts.map +1 -0
- package/dist/esm/host/identity.js +2 -0
- package/dist/esm/host/identity.js.map +1 -0
- package/dist/esm/host/index.d.ts +15 -0
- package/dist/esm/host/index.d.ts.map +1 -0
- package/dist/esm/host/index.js +9 -0
- package/dist/esm/host/index.js.map +1 -0
- package/dist/esm/host/link-ticket.d.ts +17 -0
- package/dist/esm/host/link-ticket.d.ts.map +1 -0
- package/dist/esm/host/link-ticket.js +54 -0
- package/dist/esm/host/link-ticket.js.map +1 -0
- package/dist/esm/host/oidc-token.d.ts +20 -0
- package/dist/esm/host/oidc-token.d.ts.map +1 -0
- package/dist/esm/host/oidc-token.js +93 -0
- package/dist/esm/host/oidc-token.js.map +1 -0
- package/dist/esm/host/oidc-verifier.d.ts +14 -0
- package/dist/esm/host/oidc-verifier.d.ts.map +1 -0
- package/dist/esm/host/oidc-verifier.js +63 -0
- package/dist/esm/host/oidc-verifier.js.map +1 -0
- package/dist/esm/host/serve-http.d.ts +42 -0
- package/dist/esm/host/serve-http.d.ts.map +1 -0
- package/dist/esm/host/serve-http.js +278 -0
- package/dist/esm/host/serve-http.js.map +1 -0
- package/dist/esm/host/session-broker.d.ts +45 -0
- package/dist/esm/host/session-broker.d.ts.map +1 -0
- package/dist/esm/host/session-broker.js +77 -0
- package/dist/esm/host/session-broker.js.map +1 -0
- package/dist/esm/index.d.ts +18 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +10 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/plan/expand.d.ts +37 -0
- package/dist/esm/plan/expand.d.ts.map +1 -0
- package/dist/esm/plan/expand.js +294 -0
- package/dist/esm/plan/expand.js.map +1 -0
- package/dist/esm/plan/resolve-existing.d.ts +16 -0
- package/dist/esm/plan/resolve-existing.d.ts.map +1 -0
- package/dist/esm/plan/resolve-existing.js +55 -0
- package/dist/esm/plan/resolve-existing.js.map +1 -0
- package/dist/esm/plan/split.d.ts +14 -0
- package/dist/esm/plan/split.d.ts.map +1 -0
- package/dist/esm/plan/split.js +88 -0
- package/dist/esm/plan/split.js.map +1 -0
- package/dist/esm/plan/target-state.d.ts +27 -0
- package/dist/esm/plan/target-state.d.ts.map +1 -0
- package/dist/esm/plan/target-state.js +114 -0
- package/dist/esm/plan/target-state.js.map +1 -0
- package/dist/esm/ports/elwis-api.port.d.ts +56 -0
- package/dist/esm/ports/elwis-api.port.d.ts.map +1 -0
- package/dist/esm/ports/elwis-api.port.js +12 -0
- package/dist/esm/ports/elwis-api.port.js.map +1 -0
- package/dist/esm/resolve.d.ts +23 -0
- package/dist/esm/resolve.d.ts.map +1 -0
- package/dist/esm/resolve.js +79 -0
- package/dist/esm/resolve.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BASE_DATA_TYPES = exports.PLANNABLE_KINDS = void 0;
|
|
4
|
+
exports.ResolveBaseDataType = ResolveBaseDataType;
|
|
5
|
+
exports.IsDescriptiveName = IsDescriptiveName;
|
|
6
|
+
exports.NormalizeTargetState = NormalizeTargetState;
|
|
7
|
+
exports.PLANNABLE_KINDS = [
|
|
8
|
+
'application',
|
|
9
|
+
'data-type',
|
|
10
|
+
'attribute',
|
|
11
|
+
'artifact-type',
|
|
12
|
+
'link-type',
|
|
13
|
+
];
|
|
14
|
+
exports.BASE_DATA_TYPES = [
|
|
15
|
+
'TEXT',
|
|
16
|
+
'INTEGER',
|
|
17
|
+
'DECIMAL',
|
|
18
|
+
'BOOLEAN',
|
|
19
|
+
'DATE',
|
|
20
|
+
'DATETIME',
|
|
21
|
+
'TIME',
|
|
22
|
+
'ENUMERATED_VALUE',
|
|
23
|
+
'URI',
|
|
24
|
+
'LINK',
|
|
25
|
+
'ARTIFACT',
|
|
26
|
+
];
|
|
27
|
+
const BASE_TYPE_ALIASES = {
|
|
28
|
+
text: 'TEXT',
|
|
29
|
+
string: 'TEXT',
|
|
30
|
+
varchar: 'TEXT',
|
|
31
|
+
integer: 'INTEGER',
|
|
32
|
+
int: 'INTEGER',
|
|
33
|
+
number: 'INTEGER',
|
|
34
|
+
decimal: 'DECIMAL',
|
|
35
|
+
float: 'DECIMAL',
|
|
36
|
+
double: 'DECIMAL',
|
|
37
|
+
boolean: 'BOOLEAN',
|
|
38
|
+
bool: 'BOOLEAN',
|
|
39
|
+
checkbox: 'BOOLEAN',
|
|
40
|
+
date: 'DATE',
|
|
41
|
+
datetime: 'DATETIME',
|
|
42
|
+
timestamp: 'DATETIME',
|
|
43
|
+
time: 'TIME',
|
|
44
|
+
enum: 'ENUMERATED_VALUE',
|
|
45
|
+
enumerated: 'ENUMERATED_VALUE',
|
|
46
|
+
enumeration: 'ENUMERATED_VALUE',
|
|
47
|
+
dropdown: 'ENUMERATED_VALUE',
|
|
48
|
+
uri: 'URI',
|
|
49
|
+
url: 'URI',
|
|
50
|
+
link: 'LINK',
|
|
51
|
+
artifact: 'ARTIFACT',
|
|
52
|
+
};
|
|
53
|
+
const DESCRIPTION_MARKERS = [
|
|
54
|
+
'between',
|
|
55
|
+
'them',
|
|
56
|
+
'they',
|
|
57
|
+
'that',
|
|
58
|
+
'those',
|
|
59
|
+
'these',
|
|
60
|
+
'this',
|
|
61
|
+
'each',
|
|
62
|
+
'both',
|
|
63
|
+
'other',
|
|
64
|
+
'another',
|
|
65
|
+
'it',
|
|
66
|
+
'its',
|
|
67
|
+
'their',
|
|
68
|
+
'some',
|
|
69
|
+
'any',
|
|
70
|
+
'levels',
|
|
71
|
+
'status of',
|
|
72
|
+
];
|
|
73
|
+
function ResolveBaseDataType(value) {
|
|
74
|
+
const key = value.trim().toLowerCase();
|
|
75
|
+
if (exports.BASE_DATA_TYPES.includes(value.trim().toUpperCase()))
|
|
76
|
+
return value.trim().toUpperCase();
|
|
77
|
+
return BASE_TYPE_ALIASES[key] ?? null;
|
|
78
|
+
}
|
|
79
|
+
function IsDescriptiveName(value) {
|
|
80
|
+
const words = value.trim().toLowerCase().split(/\s+/);
|
|
81
|
+
if (words.length < 2)
|
|
82
|
+
return false;
|
|
83
|
+
return DESCRIPTION_MARKERS.some((marker) => marker.includes(' ') ? value.trim().toLowerCase().includes(marker) : words.includes(marker));
|
|
84
|
+
}
|
|
85
|
+
function NormalizeTargetState(state) {
|
|
86
|
+
const entities = [];
|
|
87
|
+
const baseTypeByName = new Map();
|
|
88
|
+
for (const entity of state.entities) {
|
|
89
|
+
const name = entity.name?.trim() ?? null;
|
|
90
|
+
if (entity.kind === 'data-type' && name) {
|
|
91
|
+
const baseType = ResolveBaseDataType(name);
|
|
92
|
+
if (baseType && !entity.props?.values) {
|
|
93
|
+
baseTypeByName.set(name.toLowerCase(), baseType);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (name && IsDescriptiveName(name)) {
|
|
98
|
+
entities.push({ ...entity, name: null, described: name });
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
entities.push({ ...entity, name });
|
|
102
|
+
}
|
|
103
|
+
const resolved = entities.map((entity) => {
|
|
104
|
+
if (entity.kind !== 'attribute')
|
|
105
|
+
return entity;
|
|
106
|
+
const declared = entity.props?.dataType;
|
|
107
|
+
if (typeof declared !== 'string')
|
|
108
|
+
return entity;
|
|
109
|
+
const baseType = baseTypeByName.get(declared.toLowerCase()) ?? ResolveBaseDataType(declared);
|
|
110
|
+
if (!baseType)
|
|
111
|
+
return entity;
|
|
112
|
+
const props = { ...entity.props };
|
|
113
|
+
delete props.dataType;
|
|
114
|
+
return { ...entity, props: { ...props, baseType } };
|
|
115
|
+
});
|
|
116
|
+
const known = new Set(resolved.filter((e) => e.name).map((e) => e.name.toLowerCase()));
|
|
117
|
+
const relations = state.relations.filter((r) => known.has(r.from.trim().toLowerCase()) || r.to.trim().length > 0);
|
|
118
|
+
return { ...state, entities: resolved, relations };
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=target-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"target-state.js","sourceRoot":"","sources":["../../../src/plan/target-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResolveIdentityRole = ResolveIdentityRole;
|
|
4
|
+
function ResolveIdentityRole(systemAdmin, tenantAdmin, guest, hasUser) {
|
|
5
|
+
if (!hasUser)
|
|
6
|
+
return 'unknown';
|
|
7
|
+
if (systemAdmin)
|
|
8
|
+
return 'system-admin';
|
|
9
|
+
if (tenantAdmin)
|
|
10
|
+
return 'tenant-admin';
|
|
11
|
+
if (guest)
|
|
12
|
+
return 'guest';
|
|
13
|
+
return 'user';
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=elwis-api.port.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elwis-api.port.js","sourceRoot":"","sources":["../../../src/ports/elwis-api.port.ts"],"names":[],"mappings":";;AAuEA,kDAWC;AAXD,SAAgB,mBAAmB,CACjC,WAA2B,EAC3B,WAA2B,EAC3B,KAAqB,EACrB,OAAgB;IAEhB,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAC/B,IAAI,WAAW;QAAE,OAAO,cAAc,CAAC;IACvC,IAAI,WAAW;QAAE,OAAO,cAAc,CAAC;IACvC,IAAI,KAAK;QAAE,OAAO,OAAO,CAAC;IAC1B,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TENANT_LEVEL_KINDS = void 0;
|
|
4
|
+
exports.IsObjectId = IsObjectId;
|
|
5
|
+
exports.ResolveEntity = ResolveEntity;
|
|
6
|
+
exports.AssertInApplication = AssertInApplication;
|
|
7
|
+
exports.ResolveApplicationId = ResolveApplicationId;
|
|
8
|
+
exports.RequireApplicationId = RequireApplicationId;
|
|
9
|
+
exports.ResolveOneId = ResolveOneId;
|
|
10
|
+
exports.ResolveOneStrict = ResolveOneStrict;
|
|
11
|
+
const errors_js_1 = require("./errors.js");
|
|
12
|
+
const OID = /^[a-f0-9]{24}$/i;
|
|
13
|
+
exports.TENANT_LEVEL_KINDS = new Set(['folder', 'group', 'widget', 'workflow']);
|
|
14
|
+
function ScopeOf(kind, applicationId) {
|
|
15
|
+
return applicationId && !exports.TENANT_LEVEL_KINDS.has(kind) ? { applicationId: { $oid: applicationId } } : {};
|
|
16
|
+
}
|
|
17
|
+
function IsObjectId(value) {
|
|
18
|
+
return OID.test(value);
|
|
19
|
+
}
|
|
20
|
+
async function ResolveEntity(api, kind, applicationId, nameOrAlias) {
|
|
21
|
+
const scope = ScopeOf(kind, applicationId);
|
|
22
|
+
let rows;
|
|
23
|
+
try {
|
|
24
|
+
rows = await api.list(kind, { ...scope, $or: [{ name: nameOrAlias }, { alias: nameOrAlias }] });
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if ((0, errors_js_1.ErrorStatus)(error) === 400) {
|
|
28
|
+
rows = await api.list(kind, { ...scope, name: nameOrAlias });
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { matches: rows.map((row) => ({ id: row.id, name: row.name, alias: row.alias, kind })) };
|
|
35
|
+
}
|
|
36
|
+
async function AssertInApplication(api, kind, id, applicationId) {
|
|
37
|
+
const rows = await api.list(kind, { _id: { $oid: id }, ...ScopeOf(kind, applicationId) }, 1);
|
|
38
|
+
const first = rows[0];
|
|
39
|
+
if (!first)
|
|
40
|
+
throw new Error(`No active ${kind} ${id} in this application`);
|
|
41
|
+
return first;
|
|
42
|
+
}
|
|
43
|
+
async function ResolveApplicationId(api, applicationIdOrName) {
|
|
44
|
+
if (IsObjectId(applicationIdOrName))
|
|
45
|
+
return applicationIdOrName;
|
|
46
|
+
const rows = await api.list('application', { name: applicationIdOrName });
|
|
47
|
+
const first = rows[0];
|
|
48
|
+
if (rows.length === 1 && first)
|
|
49
|
+
return first.id;
|
|
50
|
+
if (rows.length === 0)
|
|
51
|
+
throw new errors_js_1.AmbiguityError(`No active application named "${applicationIdOrName}"`, []);
|
|
52
|
+
throw new errors_js_1.AmbiguityError(`Multiple active applications named "${applicationIdOrName}"; pass an explicit applicationId`, rows.map((row) => ({ id: row.id, name: row.name })));
|
|
53
|
+
}
|
|
54
|
+
async function RequireApplicationId(api, ref) {
|
|
55
|
+
const appRef = ref.applicationId ?? ref.applicationName;
|
|
56
|
+
if (!appRef)
|
|
57
|
+
throw new Error('Provide applicationId or applicationName');
|
|
58
|
+
return ResolveApplicationId(api, appRef);
|
|
59
|
+
}
|
|
60
|
+
async function ResolveOneId(api, kind, applicationId, ref) {
|
|
61
|
+
if (IsObjectId(ref)) {
|
|
62
|
+
await AssertInApplication(api, kind, ref, applicationId);
|
|
63
|
+
return ref;
|
|
64
|
+
}
|
|
65
|
+
const { matches } = await ResolveEntity(api, kind, applicationId, ref);
|
|
66
|
+
const first = matches[0];
|
|
67
|
+
if (matches.length === 1 && first)
|
|
68
|
+
return first.id;
|
|
69
|
+
if (matches.length === 0)
|
|
70
|
+
throw new errors_js_1.AmbiguityError(`No active ${kind} named "${ref}" in the application`, []);
|
|
71
|
+
throw new errors_js_1.AmbiguityError(`Multiple ${kind} entries named "${ref}" in the application; pass an explicit id`, matches.map((match) => ({ id: match.id, name: match.name })));
|
|
72
|
+
}
|
|
73
|
+
async function ResolveOneStrict(api, kind, applicationId, ref, options = {}) {
|
|
74
|
+
if (IsObjectId(ref)) {
|
|
75
|
+
const row = await AssertInApplication(api, kind, ref, applicationId);
|
|
76
|
+
return { id: ref, name: String(row.name ?? ref), alias: row.alias, kind };
|
|
77
|
+
}
|
|
78
|
+
const { matches } = await ResolveEntity(api, kind, applicationId, ref);
|
|
79
|
+
const first = matches[0];
|
|
80
|
+
if (matches.length === 1 && first)
|
|
81
|
+
return first;
|
|
82
|
+
if (matches.length > 1) {
|
|
83
|
+
throw new errors_js_1.AmbiguityError(`More than one ${kind} in this application is called "${ref}".`, matches.map((match) => ({ id: match.id, name: match.name })));
|
|
84
|
+
}
|
|
85
|
+
const limit = options.candidateLimit ?? 8;
|
|
86
|
+
const existing = await api.list(kind, ScopeOf(kind, applicationId), limit);
|
|
87
|
+
throw new errors_js_1.NotFoundError(kind, ref, existing.map((row) => ({ id: row.id, name: row.name })));
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/resolve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ElwisReadPort } from './ports/elwis-api.port.js';
|
|
2
|
+
export interface ScopeIdentity {
|
|
3
|
+
userId: string | null;
|
|
4
|
+
email: string | null;
|
|
5
|
+
tenantId: string | null;
|
|
6
|
+
}
|
|
7
|
+
export declare function FilterApplicationId(filter: Record<string, unknown>): string | undefined;
|
|
8
|
+
export declare class AppScope {
|
|
9
|
+
private readonly api;
|
|
10
|
+
private readonly entries;
|
|
11
|
+
private readonly expectedTenant?;
|
|
12
|
+
private ids;
|
|
13
|
+
private tenantVerified;
|
|
14
|
+
constructor(api: ElwisReadPort, entries: string[], expectedTenant?: string);
|
|
15
|
+
get restricted(): boolean;
|
|
16
|
+
identity(): Promise<ScopeIdentity | null>;
|
|
17
|
+
describe(): {
|
|
18
|
+
restricted: boolean;
|
|
19
|
+
entries: string[];
|
|
20
|
+
};
|
|
21
|
+
assertTenant(): Promise<void>;
|
|
22
|
+
allowedIds(): Promise<Set<string> | null>;
|
|
23
|
+
assertAllowed(applicationId: string): Promise<void>;
|
|
24
|
+
assertListAllowed(kind: string, filter: Record<string, unknown>): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=app-scope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-scope.d.ts","sourceRoot":"","sources":["../../src/app-scope.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAG/D,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,SAAS,CAOvF;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAgB;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAW;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAS;IACzC,OAAO,CAAC,GAAG,CAA4B;IACvC,OAAO,CAAC,cAAc,CAAS;gBAEnB,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,MAAM;IAM1E,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,QAAQ,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAIzC,QAAQ,IAAI;QAAE,UAAU,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE;IAIhD,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAS7B,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAgBzC,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMnD,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAStF"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { AppScopeError, TenantMismatchError } from './errors.js';
|
|
2
|
+
import { IsObjectId } from './resolve.js';
|
|
3
|
+
export function FilterApplicationId(filter) {
|
|
4
|
+
const value = filter.applicationId;
|
|
5
|
+
if (typeof value === 'string')
|
|
6
|
+
return value;
|
|
7
|
+
if (value && typeof value === 'object' && typeof value.$oid === 'string') {
|
|
8
|
+
return value.$oid;
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
export class AppScope {
|
|
13
|
+
api;
|
|
14
|
+
entries;
|
|
15
|
+
expectedTenant;
|
|
16
|
+
ids = null;
|
|
17
|
+
tenantVerified = false;
|
|
18
|
+
constructor(api, entries, expectedTenant) {
|
|
19
|
+
this.api = api;
|
|
20
|
+
this.entries = entries;
|
|
21
|
+
this.expectedTenant = expectedTenant;
|
|
22
|
+
}
|
|
23
|
+
get restricted() {
|
|
24
|
+
return this.entries.length > 0;
|
|
25
|
+
}
|
|
26
|
+
identity() {
|
|
27
|
+
return this.api.whoAmI().catch(() => null);
|
|
28
|
+
}
|
|
29
|
+
describe() {
|
|
30
|
+
return { restricted: this.restricted, entries: [...this.entries] };
|
|
31
|
+
}
|
|
32
|
+
async assertTenant() {
|
|
33
|
+
if (!this.expectedTenant || this.tenantVerified)
|
|
34
|
+
return;
|
|
35
|
+
const identity = await this.identity();
|
|
36
|
+
if (identity?.tenantId !== this.expectedTenant) {
|
|
37
|
+
throw new TenantMismatchError(this.expectedTenant, identity?.tenantId ?? null);
|
|
38
|
+
}
|
|
39
|
+
this.tenantVerified = true;
|
|
40
|
+
}
|
|
41
|
+
async allowedIds() {
|
|
42
|
+
if (!this.restricted)
|
|
43
|
+
return null;
|
|
44
|
+
if (this.ids)
|
|
45
|
+
return this.ids;
|
|
46
|
+
const ids = new Set();
|
|
47
|
+
for (const entry of this.entries) {
|
|
48
|
+
if (IsObjectId(entry)) {
|
|
49
|
+
ids.add(entry);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const rows = await this.api.list('application', { name: entry });
|
|
53
|
+
for (const row of rows)
|
|
54
|
+
ids.add(row.id);
|
|
55
|
+
}
|
|
56
|
+
this.ids = ids;
|
|
57
|
+
return ids;
|
|
58
|
+
}
|
|
59
|
+
async assertAllowed(applicationId) {
|
|
60
|
+
await this.assertTenant();
|
|
61
|
+
const ids = await this.allowedIds();
|
|
62
|
+
if (ids && !ids.has(applicationId))
|
|
63
|
+
throw new AppScopeError(applicationId);
|
|
64
|
+
}
|
|
65
|
+
async assertListAllowed(kind, filter) {
|
|
66
|
+
await this.assertTenant();
|
|
67
|
+
if (!this.restricted || kind === 'application')
|
|
68
|
+
return;
|
|
69
|
+
const applicationId = FilterApplicationId(filter);
|
|
70
|
+
if (!applicationId) {
|
|
71
|
+
throw new AppScopeError('unspecified, when an application allowlist is set, list calls require an applicationId filter');
|
|
72
|
+
}
|
|
73
|
+
await this.assertAllowed(applicationId);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=app-scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-scope.js","sourceRoot":"","sources":["../../src/app-scope.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ResolvedIdentity, TokenVerifier, McpServerLike } from '../host/identity.js';
|
|
2
|
+
export declare function RunHostConformance(factory: (identity: ResolvedIdentity) => McpServerLike, verifier: TokenVerifier): Promise<void>;
|
|
3
|
+
//# sourceMappingURL=host.conformance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.conformance.d.ts","sourceRoot":"","sources":["../../../src/conformance/host.conformance.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAwF1F,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,aAAa,EACtD,QAAQ,EAAE,aAAa,GACtB,OAAO,CAAC,IAAI,CAAC,CAsBf"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { ServeMcpHttp } from '../host/serve-http.js';
|
|
2
|
+
function assert(condition, message) {
|
|
3
|
+
if (!condition)
|
|
4
|
+
throw new Error(message);
|
|
5
|
+
}
|
|
6
|
+
const FETCH_TIMEOUT_MS = 5_000;
|
|
7
|
+
function ParseJsonRpcFrame(contentType, body) {
|
|
8
|
+
if (contentType?.includes('text/event-stream')) {
|
|
9
|
+
const dataLine = body.split('\n').find((line) => line.startsWith('data:'));
|
|
10
|
+
assert(dataLine, `expected an SSE data frame in the response, got: ${body}`);
|
|
11
|
+
return JSON.parse(dataLine.slice('data:'.length).trim());
|
|
12
|
+
}
|
|
13
|
+
return JSON.parse(body);
|
|
14
|
+
}
|
|
15
|
+
function AdvertisedMetadataPath(header) {
|
|
16
|
+
const match = /resource_metadata="([^"]+)"/.exec(header ?? '');
|
|
17
|
+
const url = match?.[1];
|
|
18
|
+
assert(url, 'the challenge did not advertise a resource_metadata url');
|
|
19
|
+
return new URL(url).pathname;
|
|
20
|
+
}
|
|
21
|
+
const HOST_CONFORMANCE = [
|
|
22
|
+
{
|
|
23
|
+
id: 'unauthenticated-call-challenged',
|
|
24
|
+
title: 'a call with no bearer token is challenged, not silently served',
|
|
25
|
+
why: 'A server that skips the challenge on a missing token would serve tool calls to anyone who can reach the port, defeating authentication entirely.',
|
|
26
|
+
async run(base) {
|
|
27
|
+
const res = await fetch(`${base}/mcp`, { method: 'POST', body: '{}', signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
28
|
+
assert(res.status === 401, `expected 401 for an unauthenticated call, got ${res.status}`);
|
|
29
|
+
AdvertisedMetadataPath(res.headers.get('www-authenticate'));
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'metadata-discoverable-at-advertised-url',
|
|
34
|
+
title: 'the url the challenge advertises actually serves resource metadata',
|
|
35
|
+
why: 'A client discovers where to authenticate by following resource_metadata from the challenge. If that route is missing or disconnected from the advertised url, discovery dead-ends and no client can ever authenticate.',
|
|
36
|
+
async run(base) {
|
|
37
|
+
const challenged = await fetch(`${base}/mcp`, { method: 'POST', body: '{}', signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
38
|
+
const metadataPath = AdvertisedMetadataPath(challenged.headers.get('www-authenticate'));
|
|
39
|
+
const metadata = await fetch(`${base}${metadataPath}`, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
40
|
+
assert(metadata.status === 200, `expected 200 fetching the advertised metadata url, got ${metadata.status}`);
|
|
41
|
+
const body = await metadata.json();
|
|
42
|
+
assert(typeof body.resource === 'string', 'metadata document is missing a resource field');
|
|
43
|
+
assert(Array.isArray(body.authorization_servers), 'metadata document is missing authorization_servers');
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'authenticated-call-served',
|
|
48
|
+
title: 'a call bearing a token the verifier accepts is served',
|
|
49
|
+
why: 'This is the point of hosting a server at all: the transport must actually be wired to the factory-built server, not merely accept the connection and then never respond. A host that calls handleRequest without first connecting the server still answers 200 with SSE headers and then hangs, so the body must be read and parsed as JSON-RPC, not just the status code checked.',
|
|
50
|
+
async run(base) {
|
|
51
|
+
const listed = await fetch(`${base}/mcp`, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: {
|
|
54
|
+
authorization: 'Bearer conformance-token',
|
|
55
|
+
'content-type': 'application/json',
|
|
56
|
+
accept: 'application/json, text/event-stream',
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list' }),
|
|
59
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
60
|
+
});
|
|
61
|
+
assert(listed.status === 200, `expected 200 for an authenticated call, got ${listed.status}`);
|
|
62
|
+
const body = await listed.text();
|
|
63
|
+
const frame = ParseJsonRpcFrame(listed.headers.get('content-type'), body);
|
|
64
|
+
assert(frame.jsonrpc === '2.0', `expected a jsonrpc 2.0 response, got: ${body}`);
|
|
65
|
+
assert(frame.id === 1, `expected the response to answer request id 1, got: ${body}`);
|
|
66
|
+
assert('result' in frame || 'error' in frame, `expected a result or error frame, got: ${body}`);
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
export async function RunHostConformance(factory, verifier) {
|
|
71
|
+
const handle = await ServeMcpHttp({
|
|
72
|
+
factory,
|
|
73
|
+
verifier,
|
|
74
|
+
resource: 'http://127.0.0.1/mcp',
|
|
75
|
+
authorizationServers: ['https://id.example.com'],
|
|
76
|
+
port: 0,
|
|
77
|
+
});
|
|
78
|
+
try {
|
|
79
|
+
const base = `http://127.0.0.1:${handle.port}`;
|
|
80
|
+
const failures = [];
|
|
81
|
+
for (const hostCase of HOST_CONFORMANCE) {
|
|
82
|
+
try {
|
|
83
|
+
await hostCase.run(base);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
failures.push(`${hostCase.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
assert(failures.length === 0, `host conformance failed:\n${failures.join('\n')}`);
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
await handle.close();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=host.conformance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.conformance.js","sourceRoot":"","sources":["../../../src/conformance/host.conformance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ElwisReadPort, EntityKind } from '../ports/elwis-api.port.js';
|
|
2
|
+
export interface ConformanceFixtures {
|
|
3
|
+
applicationId: string;
|
|
4
|
+
applicationName: string;
|
|
5
|
+
kind: EntityKind;
|
|
6
|
+
knownEntityName: string;
|
|
7
|
+
knownEntityId: string;
|
|
8
|
+
absentEntityName?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ConformanceCase {
|
|
11
|
+
id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
why: string;
|
|
14
|
+
run(api: ElwisReadPort, fixtures: ConformanceFixtures): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export declare const READ_PORT_CONFORMANCE: ConformanceCase[];
|
|
17
|
+
export interface ConformanceResult {
|
|
18
|
+
id: string;
|
|
19
|
+
title: string;
|
|
20
|
+
passed: boolean;
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function RunReadPortConformance(api: ElwisReadPort, fixtures: ConformanceFixtures): Promise<ConformanceResult[]>;
|
|
24
|
+
//# sourceMappingURL=read-port.conformance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-port.conformance.d.ts","sourceRoot":"","sources":["../../../src/conformance/read-port.conformance.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAG5E,MAAM,WAAW,mBAAmB;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,UAAU,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAMD,eAAO,MAAM,qBAAqB,EAAE,eAAe,EAqGlD,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,aAAa,EAClB,QAAQ,EAAE,mBAAmB,GAC5B,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAgB9B"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { ErrorStatus, NotFoundError } from '../errors.js';
|
|
2
|
+
import { ResolveApplicationId, ResolveEntity, ResolveOneStrict } from '../resolve.js';
|
|
3
|
+
function assert(condition, message) {
|
|
4
|
+
if (!condition)
|
|
5
|
+
throw new Error(message);
|
|
6
|
+
}
|
|
7
|
+
export const READ_PORT_CONFORMANCE = [
|
|
8
|
+
{
|
|
9
|
+
id: 'excludes-soft-deleted',
|
|
10
|
+
title: 'list never returns soft-deleted rows',
|
|
11
|
+
why: 'The HTTP adapter injects deleted:null for free. An adapter reading collections directly does not, and would leak deleted entities into resolution and into pickers.',
|
|
12
|
+
async run(api, fx) {
|
|
13
|
+
const rows = await api.list(fx.kind, { applicationId: { $oid: fx.applicationId } }, 200);
|
|
14
|
+
const leaked = rows.filter((row) => row.deleted !== null && row.deleted !== undefined);
|
|
15
|
+
assert(leaked.length === 0, `list returned ${leaked.length} soft-deleted ${fx.kind} rows`);
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'scopes-by-application',
|
|
20
|
+
title: 'list honours an applicationId filter',
|
|
21
|
+
why: 'Application scoping is the only thing preventing cross-application reads.',
|
|
22
|
+
async run(api, fx) {
|
|
23
|
+
const rows = await api.list(fx.kind, { applicationId: { $oid: fx.applicationId } }, 200);
|
|
24
|
+
const foreign = rows.filter((row) => row.applicationId !== undefined && String(row.applicationId) !== fx.applicationId);
|
|
25
|
+
assert(foreign.length === 0, `list returned ${foreign.length} rows from another application`);
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'honours-limit',
|
|
30
|
+
title: 'list honours the limit argument',
|
|
31
|
+
why: 'Silent over-return breaks pagination honesty and can blow the model context.',
|
|
32
|
+
async run(api, fx) {
|
|
33
|
+
const rows = await api.list(fx.kind, { applicationId: { $oid: fx.applicationId } }, 1);
|
|
34
|
+
assert(rows.length <= 1, `limit 1 returned ${rows.length} rows`);
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'or-filter-or-400',
|
|
39
|
+
title: 'list either supports $or on name/alias or rejects it with status 400',
|
|
40
|
+
why: 'ResolveEntity falls back to a name-only lookup on 400. An adapter that throws an untyped error, or silently returns nothing, breaks alias resolution without any signal.',
|
|
41
|
+
async run(api, fx) {
|
|
42
|
+
try {
|
|
43
|
+
const rows = await api.list(fx.kind, {
|
|
44
|
+
applicationId: { $oid: fx.applicationId },
|
|
45
|
+
$or: [{ name: fx.knownEntityName }, { alias: fx.knownEntityName }],
|
|
46
|
+
});
|
|
47
|
+
assert(Array.isArray(rows), '$or lookup did not return an array');
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
assert(ErrorStatus(error) === 400, `$or lookup failed with a non-400, untyped error: ${String(error)}`);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'get-one-addresses-entity',
|
|
56
|
+
title: 'getOne returns the addressed entity',
|
|
57
|
+
why: 'Read-back verification depends on addressing a single entity by id.',
|
|
58
|
+
async run(api, fx) {
|
|
59
|
+
const row = await api.getOne(fx.kind, fx.knownEntityId);
|
|
60
|
+
const id = row.id;
|
|
61
|
+
assert(String(id) === fx.knownEntityId, `getOne returned id ${String(id)}, expected ${fx.knownEntityId}`);
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'identity-has-tenant',
|
|
66
|
+
title: 'whoAmI reports a tenant and a role',
|
|
67
|
+
why: 'Every access decision keys off tenant and role. An adapter that leaves tenantId null degrades callers to an anonymous bucket.',
|
|
68
|
+
async run(api) {
|
|
69
|
+
const identity = await api.whoAmI();
|
|
70
|
+
assert(typeof identity.role === 'string', 'identity.role missing');
|
|
71
|
+
assert(identity.tenantId !== undefined, 'identity.tenantId missing');
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'resolve-by-name',
|
|
76
|
+
title: 'ResolveEntity finds a known entity by name',
|
|
77
|
+
why: 'Name resolution is the contract that lets a person type a name instead of an id.',
|
|
78
|
+
async run(api, fx) {
|
|
79
|
+
const { matches } = await ResolveEntity(api, fx.kind, fx.applicationId, fx.knownEntityName);
|
|
80
|
+
assert(matches.some((match) => match.id === fx.knownEntityId), `did not resolve "${fx.knownEntityName}" to ${fx.knownEntityId}`);
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'resolve-application-by-name',
|
|
85
|
+
title: 'ResolveApplicationId maps an application name to its id',
|
|
86
|
+
why: 'Every application-scoped call starts here.',
|
|
87
|
+
async run(api, fx) {
|
|
88
|
+
const id = await ResolveApplicationId(api, fx.applicationName);
|
|
89
|
+
assert(id === fx.applicationId, `resolved ${id}, expected ${fx.applicationId}`);
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'not-found-carries-candidates',
|
|
94
|
+
title: 'ResolveOneStrict reports a miss as NotFoundError with candidates',
|
|
95
|
+
why: 'A miss must offer what does exist. Surfacing a validator message instead is the defect that produced wrong-entity mutations.',
|
|
96
|
+
async run(api, fx) {
|
|
97
|
+
const absent = fx.absentEntityName ?? '__definitely_absent_entity__';
|
|
98
|
+
try {
|
|
99
|
+
await ResolveOneStrict(api, fx.kind, fx.applicationId, absent);
|
|
100
|
+
throw new Error('ResolveOneStrict resolved a name that should not exist');
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
assert(error instanceof NotFoundError, `expected NotFoundError, got ${String(error)}`);
|
|
104
|
+
assert(Array.isArray(error.candidates), 'NotFoundError carried no candidates array');
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
export async function RunReadPortConformance(api, fixtures) {
|
|
110
|
+
const results = [];
|
|
111
|
+
for (const testCase of READ_PORT_CONFORMANCE) {
|
|
112
|
+
try {
|
|
113
|
+
await testCase.run(api, fixtures);
|
|
114
|
+
results.push({ id: testCase.id, title: testCase.title, passed: true });
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
results.push({
|
|
118
|
+
id: testCase.id,
|
|
119
|
+
title: testCase.title,
|
|
120
|
+
passed: false,
|
|
121
|
+
error: error instanceof Error ? error.message : String(error),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return results;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=read-port.conformance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-port.conformance.js","sourceRoot":"","sources":["../../../src/conformance/read-port.conformance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAkBtF,SAAS,MAAM,CAAC,SAAkB,EAAE,OAAe;IACjD,IAAI,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAsB;IACtD;QACE,EAAE,EAAE,uBAAuB;QAC3B,KAAK,EAAE,sCAAsC;QAC7C,GAAG,EAAE,qKAAqK;QAC1K,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE;YACf,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,aAAa,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;YACzF,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,KAAK,IAAI,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC;YACvF,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,iBAAiB,MAAM,CAAC,MAAM,iBAAiB,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC;QAC7F,CAAC;KACF;IACD;QACE,EAAE,EAAE,uBAAuB;QAC3B,KAAK,EAAE,sCAAsC;QAC7C,GAAG,EAAE,2EAA2E;QAChF,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE;YACf,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,aAAa,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;YACzF,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CACzB,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,aAAa,KAAK,SAAS,IAAI,MAAM,CAAE,GAAmC,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC,aAAa,CAC5H,CAAC;YACF,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,iBAAiB,OAAO,CAAC,MAAM,gCAAgC,CAAC,CAAC;QAChG,CAAC;KACF;IACD;QACE,EAAE,EAAE,eAAe;QACnB,KAAK,EAAE,iCAAiC;QACxC,GAAG,EAAE,8EAA8E;QACnF,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE;YACf,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,aAAa,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;YACvF,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,oBAAoB,IAAI,CAAC,MAAM,OAAO,CAAC,CAAC;QACnE,CAAC;KACF;IACD;QACE,EAAE,EAAE,kBAAkB;QACtB,KAAK,EAAE,sEAAsE;QAC7E,GAAG,EAAE,0KAA0K;QAC/K,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE;YACf,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE;oBACnC,aAAa,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,aAAa,EAAE;oBACzC,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,eAAe,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,eAAe,EAAE,CAAC;iBACnE,CAAC,CAAC;gBACH,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,oCAAoC,CAAC,CAAC;YACpE,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,oDAAoD,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC1G,CAAC;QACH,CAAC;KACF;IACD;QACE,EAAE,EAAE,0BAA0B;QAC9B,KAAK,EAAE,qCAAqC;QAC5C,GAAG,EAAE,qEAAqE;QAC1E,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE;YACf,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC;YACxD,MAAM,EAAE,GAAI,GAAwB,CAAC,EAAE,CAAC;YACxC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,aAAa,EAAE,sBAAsB,MAAM,CAAC,EAAE,CAAC,cAAc,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;QAC5G,CAAC;KACF;IACD;QACE,EAAE,EAAE,qBAAqB;QACzB,KAAK,EAAE,oCAAoC;QAC3C,GAAG,EAAE,+HAA+H;QACpI,KAAK,CAAC,GAAG,CAAC,GAAG;YACX,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;YACpC,MAAM,CAAC,OAAO,QAAQ,CAAC,IAAI,KAAK,QAAQ,EAAE,uBAAuB,CAAC,CAAC;YACnE,MAAM,CAAC,QAAQ,CAAC,QAAQ,KAAK,SAAS,EAAE,2BAA2B,CAAC,CAAC;QACvE,CAAC;KACF;IACD;QACE,EAAE,EAAE,iBAAiB;QACrB,KAAK,EAAE,4CAA4C;QACnD,GAAG,EAAE,kFAAkF;QACvF,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE;YACf,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC;YAC5F,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,aAAa,CAAC,EAAE,oBAAoB,EAAE,CAAC,eAAe,QAAQ,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;QACnI,CAAC;KACF;IACD;QACE,EAAE,EAAE,6BAA6B;QACjC,KAAK,EAAE,yDAAyD;QAChE,GAAG,EAAE,4CAA4C;QACjD,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE;YACf,MAAM,EAAE,GAAG,MAAM,oBAAoB,CAAC,GAAG,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC;YAC/D,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;QAClF,CAAC;KACF;IACD;QACE,EAAE,EAAE,8BAA8B;QAClC,KAAK,EAAE,kEAAkE;QACzE,GAAG,EAAE,8HAA8H;QACnI,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE;YACf,MAAM,MAAM,GAAG,EAAE,CAAC,gBAAgB,IAAI,8BAA8B,CAAC;YACrE,IAAI,CAAC;gBACH,MAAM,gBAAgB,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;gBAC/D,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;YAC5E,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,KAAK,YAAY,aAAa,EAAE,+BAA+B,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBACvF,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,2CAA2C,CAAC,CAAC;YACvF,CAAC;QACH,CAAC;KACF;CACF,CAAC;AASF,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,GAAkB,EAClB,QAA6B;IAE7B,MAAM,OAAO,GAAwB,EAAE,CAAC;IACxC,KAAK,MAAM,QAAQ,IAAI,qBAAqB,EAAE,CAAC;QAC7C,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;YAClC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACzE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC;gBACX,EAAE,EAAE,QAAQ,CAAC,EAAE;gBACf,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|