First a quick note: you aren't using the term you've defined in the context in the input object. Since you're using the full URI, the @type definition is not being applied. Instead you should use the term (x:purpose):
{
"@context": {
"@base": "file:///",
"x": "https://example.org/pub/x#",
"x-attribute": "https://example.org/pub/x-attribute#",
"x:purpose": {
"@type": "@id"
}
},
"x:purpose": "https://example.org/pub/x-attribute#on"
}
If you don't use the term in the data, you'll need to specify that the value is an @id like so:
{
"@context": {
"@base": "file:///",
"x": "https://example.org/pub/x#",
"x-attribute": "https://example.org/pub/x-attribute#",
"x:purpose": {
"@type": "@id"
}
},
"https://example.org/pub/x#purpose": {
"@id": "https://example.org/pub/x-attribute#on"
}
}
Now, to get the effect you want where the value is compacted to a CURIE, you must indicate that the value is actually part of your vocabulary (an "enum" if you will). You do this by changing the new context to:
{
"x": "https://example.org/pub/x#",
"x-attribute": "https://example.org/pub/x-attribute#",
"x:purpose": {
"@type": "@vocab"
}
}
That should give you the result you want.