resource-access-offers-generator
Skill42 starsChanged 50 days agoMITView on GitHub
Generate RDF Turtle offer/license/price bundles for File Access, Graph Access, API Access, and Chat Service products. Trigger on phrases like 'generate offers', 'create license bundle', 'make a file access offer', 'generate graph access offer', 'create API access offers', 'create chat service offers', or any request to produce resource-access offers in Turtle format.
In this repository45 other files it gives its agents.
- agent-rdf-memory/AGENTS.mdAGENTS.md
- AGENTS.mdAGENTS.md
- a2a-clientSkill
- acp-clientSkill
- csv-to-rdf-det-generatorSkill
- data-twinglerSkill
- dbpedia-query-skillSkill
- document-to-kg-skillSkill
- fediverse-crudSkill
- fuxi-engineerSkill
- infographic-describerSkill
- iodbc-dsn-managerSkill
- kg-generatorSkill
- linked-data-skillsSkill
- llm-routing-skillSkill
- monidSkill
- mpp-stripe-clientSkill
- mtls-curlSkill
- opal-agent-skill-assemblerSkill
- openlink-license-managerSkill
- openlink-license-readerSkill
- openlink-request-broker-configuratorSkill
- opml-rss-readerSkill
- osdi-inclusion-engineSkill
- pinchtabSkill
- rdf-det-variant-generatorSkill
- rdf-infographic-skillSkill
- rss-feed-generatorSkill
- s3-query-skillSkill
- screencast-recorderSkill
- set-webdav-resource-propertySkill
- sparql-faceted-browserSkill
- ucp-clientSkill
- uriburner-opal-agent-skillsSkill
- virtuoso-rdf-bulk-loaderSkill
- virtuoso-rdf-loaderSkill
- virtuoso-support-agentSkill
- wc2026-match-reportSkill
- weblog-from-webdavSkill
- website-from-webdavSkill
- wikidata-query-skillSkill
- world-cup-2026-navigatorSkill
- x402-buyerSkill
- xmla-clientSkill
- youidSkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 0 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 22
Run Post-Generation Checklist, save to the output directory the user specifies for this session (ask if not already given — this is …
- Very specific: line 70
All the literal values shown ("File Access"@en, "ACL controlled file access"@en, "WebDAV Service"@en) are fixed conventions, including the …
- Very specific: line 215
Every Offer needs skos:prefLabel (gate-enforced for API Access specifically — this isn't required on Offers generally, only on Licenses).
- Very specific: line 243
Every Offer needs skos:prefLabel (same as API Access — not required on Offers generally).
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Resource Access Offers Generator
Generate valid RDF Turtle documents containing File Access, Graph Access, API Access, or Chat Service offers.
Minimum Input Requirements
| Input | Required | Example |
| License resource IRI | Yes (not for Chat Service — see its own section) | URL of protected file/graph |
| Price | Yes | 2.99 |
| Description | Yes | Free text — name/prefLabel/comment auto-derived |
| Host platform | No (default: URIBurner) | linkeddata.uriburner.com |
Host Platform Profiles
URIBurner (linkeddata.uriburner.com), ODS-QA (ods-qa.openlinksw.com), Localhost (localhost)
Workflow
- Elicit, as separate required inputs: resource type (file/graph/api/chatservice), pricing model (one-time purchase vs. recurring subscription — a distinct question from resource type; never infer it silently from how the price happens to be phrased), host, license IRI, price (plus billing interval/unit when the pricing model is subscription), description
- Derive name, prefLabel, comment from description
- Load prompt template from
prompts/ - Substitute placeholders, generate Turtle
- Validate syntax (rdflib), validate SHACL (
scripts/validate-offers-shacl.py) - Run Post-Generation Checklist, save to the output directory the user specifies for this session (ask if not already given — this is user/machine-specific, e.g., previously
/Users/jacqui/source/ttl/my_git_forks/virtuoso-82-offers-description/for JacquiHand's machine, which won't exist on another contributor's machine), provide loading instructions
Document Metadata Block (source:)
Immediately after the @prefix declarations, every generated file MUST open with a schema:CreativeWork block describing the Turtle file being generated itself — never the licensed resource (file/graph/API being sold). Declare:
@prefix source: <http://virtuoso.openlinksw.com/data/turtle/{output-filename}> .
@prefix : <#> .
source: a schema:CreativeWork ;
schema:name "{Document title}" ;
schema:description "{What this document's offers cover}" ;
schema:author <http://www.openlinksw.com/#this> ;
cc:attributionName "OpenLink Software" ;
schema:datePublished "{dateTime}"^^xsd:dateTime ;
schema:dateModified "{dateTime}"^^xsd:dateTime .{output-filename} is the actual filename this Turtle file will be saved as (matching the convention in the two prior art files: http://virtuoso.openlinksw.com/data/turtle/FileAccessOffers-License-Prices.ttl, http://virtuoso.openlinksw.com/data/turtle/OPALOffers-Licenses-Prices-ods-qa-SPARQL-API.ttl) — this host/path is fixed regardless of which host platform profile the licensed resource lives on.
Every generated Offer, License, and PriceSpecification node MUST carry wdrs:describedby source: pointing at this document node — that relation is what "identifies" the document as their source. The licensed resource itself is never separately described as a schema:CreativeWork — it's referenced only via opllic:uriParameter/opllic:graphParameter. The SHACL gate enforces wdrs:describedby (sh:class schema:CreativeWork) as required on Offer, License, and PriceSpecification shapes, and DocumentShape requires schema:name/schema:author/schema:datePublished/schema:dateModified on whatever node that resolves to.
Entity IRI Naming Convention
The Offer, License, and PriceSpecification each get their own dedicated path segment — see references/offer-iri-patterns.md for the full template table and host_suffix derivation:
- PriceSpecification:
http://data.openlinksw.com/oplweb/offer-unitprice/{OfferIdentifier}PriceSpecification#this— no host suffix. - License:
http://data.openlinksw.com/oplweb/license/{OfferIdentifier}License{host_suffix}#this - Offer:
http://data.openlinksw.com/oplweb/offer/{OfferIdentifier}Offer{host_suffix}#this
{OfferIdentifier} is a PascalCase name for the specific thing being offered (e.g., DataTwinglerSpecificModuleEntryLevel), derived from the offer's subject — not an ad-hoc slug and not reusing the Product's IRI shape. {host_suffix} is host_short with its first letter capitalized (URIBurner → URIBurner, ods-qa → Ods-qa, localhost → Localhost). The SHACL gate enforces the path segment and substring for each (sh:pattern on the shape itself, checked against the focus node's own IRI) — an Offer/License/PriceSpecification IRI that doesn't follow this shape now fails, even if every other property is correct.
File Access Product (WebAPI) Description Template
The Product node for a File Access offer MUST follow this full template — not a minimal stub:
<http://data.openlinksw.com/oplweb/{host_short}FA#this> a schema:WebAPI ;
schema:name "File access via {host_short}"@en ;
schema:applicationCategory "File Access"@en ;
schema:applicationSubCategory "ACL controlled file access"@en ;
schema:description """Attribute-based, access control constrained access to a File hosted by {host_hostname}."""@en ;
skos:related <http://data.openlinksw.com/oplweb/{host_short}OPAL#this> ;
schema:provider [
a schema:Organization ;
schema:name "OpenLink Software"@en ;
schema:url <https://www.openlinksw.com/> ;
] ;
schema:url <https://{host_hostname}/> ;
schema:hasPart [
a schema:WebAPI ;
schema:name "WebDAV Service Endpoint"@en ;
schema:description """WebDAV Endpoint for file interactions."""@en ;
schema:serviceType "WebDAV Service"@en ;
schema:url <https://{host_hostname}/DAV>
] .Notes:
- All the literal values shown (
"File Access"@en,"ACL controlled file access"@en,"WebDAV Service"@en) are fixed conventions, including the@enlanguage tag — the gate checks exact term equality, so a plain string without the tag fails. schema:urlon the top-level Product is the host root (https://{host_hostname}/), not the/DAVpath — the/DAVendpoint belongs to the nestedschema:hasPartsub-resource'sschema:url.skos:relatedpoints at the OPAL ACL server Product (http://data.openlinksw.com/oplweb/{host_short}OPAL#this, seereferences/offer-iri-patterns.md) — required because an OPAL server is what actually creates the ACLs backing file access.- The SHACL gate targets this shape via
sh:targetSubjectsOf schema:applicationCategoryrather thansh:targetClass schema:WebAPI— the nested WebDAV sub-resource is alsoa schema:WebAPIbut has noschema:applicationCategory, so it's correctly excluded from this shape and validated only by its own (lighter) constraints (schema:name,schema:description,schema:serviceType "WebDAV Service"@en,schema:url).
Graph Access Product (WebAPI+Service) Description Template
The Product node for a Graph Access offer MUST follow this full template — not a minimal stub. It mirrors the File Access template one-for-one, substituting the SPARQL/knowledge-graph vocabulary:
<http://data.openlinksw.com/oplweb/{host_short}DA#this> a schema:WebAPI, schema:Service ;
schema:name "Knowledge graph access via {host_short}"@en ;
schema:applicationCategory "Data Access"@en ;
schema:applicationSubCategory "ACL controlled knowledge graph access"@en ;
schema:description """Attribute-based, access control constrained SPARQL access to a named graph hosted by {host_hostname}."""@en ;
skos:related <http://data.openlinksw.com/oplweb/{host_short}OPAL#this> ;
schema:provider [
a schema:Organization ;
schema:name "OpenLink Software"@en ;
schema:url <https://www.openlinksw.com/> ;
] ;
schema:url <https://{host_hostname}/> ;
schema:hasPart [
a schema:WebAPI ;
schema:name "SPARQL Query Service Endpoint"@en ;
schema:description """HTTP SPARQL Query Service Endpoint providing access to the named graph."""@en ;
schema:serviceType "SPARQL Query Service"@en ;
schema:url <https://{host_hostname}/sparql>
] .Notes (identical reasoning to the File Access template, see references/offer-iri-patterns.md for the {host_short}DA#this / {host_short}OPAL#this IRI patterns):
- Fixed-convention literals (
"Data Access"@en,"ACL controlled knowledge graph access"@en,"SPARQL Query Service"@en) all need the@entag — exact term equality. schema:urlon the top-level Product is the host root, not/sparql— the SPARQL endpoint path belongs on the nestedschema:hasPartsub-resource'sschema:url.- The Product is typed both
schema:WebAPIandschema:Service(unlike File Access, which isschema:WebAPIonly). - Same
sh:targetSubjectsOf schema:applicationCategoryscoping trick as File Access — the nested SPARQL sub-resource is alsoa schema:WebAPIbut has noapplicationCategory, so it's excluded from this shape and validated only by its own lighter nested constraints. - The License's
opllic:graphParametershould be the actual named graph IRI being sold access to (e.g.,urn:jch:test), not the SPARQL endpoint URL — the endpoint URL belongs on the Product'shasPartsub-resource. A sampleopllic:uriParameterquery URL scopingdefault-graph-urito that graph is good practice (see the two prior-art reference files) but not gate-enforced.
Graph Access Authorization Block (ConditionalGroup + acl:Authorization)
Every generated Graph Access offer file MUST also include, in the same output file, a self-contained authorization block granting the purchaser actual read access to the sold graph — otherwise the offer/license/price bundle exists but nothing enforces it. This mirrors the pattern in the prior-art Offers-authorizations-and-restrictions.ttl (see oplacl:demo-graph-access-world-cup-meshup / <.../group/DemoGraphAccessUsersWorldCupMeshup#this>), reusing the same oplofr:{OfferIdentifier}Offer resource-specific type from the Entity IRI/Type conventions above as the purchase-check predicate:
<http://data.openlinksw.com/oplweb/group/{OfferIdentifier}GraphAccessUsers#this> a oplacl:ConditionalGroup ;
foaf:name "Users identified using a NetID based Identifier who have purchased the {offer name} offer and whose license is in date" ;
oplacl:hasCondition [
a oplacl:GroupCondition, oplacl:QueryCondition ;
oplacl:hasQuery """prefix oplprchs: <http://www.openlinksw.com/ontology/purchases#>
prefix opllic: <http://www.openlinksw.com/ontology/licenses#>
prefix oplofr: <http://www.openlinksw.com/ontology/offers#>
prefix schema: <http://schema.org/>
prefix oplshop: <http://www.openlinksw.com/ontology/shop#>
prefix owl: <http://www.w3.org/2002/07/owl#>
ask where {
{ graph <urn:openlinksw.com.shop:registry> { ?shop oplshop:hasPurchaseCache ?cg } }
union
{ bind(<urn:openlinksw.com:shop:purchases:cache> as ?cg) }
graph ?cg {
{ ^{uri}^ oplprchs:madePurchase ?purchase . }
union
{ ?o owl:sameAs ^{uri}^ ; oplprchs:madePurchase ?purchase . }
?purchase oplprchs:contains ?offer ;
oplprchs:purchaseDate ?purchaseDate .
?offer a oplofr:{OfferIdentifier}Offer ;
schema:itemOffered ?license .
?license opllic:hasDuration ?duration .
optional {?duration opllic:durationYears ?years .}
filter ((?duration like <http://data.openlinksw.com/oplweb/license/License-Duration#ongoing-subscription> ) or
(bif:dateadd ('day', xsd:integer(?years) * 365, ?purchaseDate) > bif:now()) ) .
}
}"""
] .
<http://data.openlinksw.com/oplweb/acl/{OfferIdentifier}GraphAccess#this> a acl:Authorization ;
schema:name "Rule to allow access to the <{graph IRI}> graph" ;
acl:accessTo <{graph IRI}> ;
oplacl:hasAccessMode oplacl:Read ;
oplacl:hasRealm oplacl:DefaultRealm ;
oplacl:hasScope oplacl:PrivateGraphs ;
acl:agent <http://data.openlinksw.com/oplweb/group/{OfferIdentifier}GraphAccessUsers#this> ;
wdrs:describedby source: .Notes:
{graph IRI}inacl:accessToMUST be the exact same IRI as the License'sopllic:graphParameter— this is what actually ties the purchase to real access. The SHACL gate enforces this cross-link with ash:sparqlconstraint onGraphAccessOfferShape: it fails if noacl:Authorizationexists withacl:accessToequal to the Offer's License'sgraphParameterandoplacl:hasAccessMode oplacl:Read.- The embedded SPARQL
ASKquery text insideoplacl:hasQueryis NOT gate-validated for content (it's a plain string literal, and string-matching against arbitrarily-formatted embedded SPARQL would be too fragile) — get theoplofr:{OfferIdentifier}Offerreference right by hand, matching the resource-specific type actually used on the Offer. oplacl:ConditionalGroup(needsfoaf:nameandoplacl:hasCondition/oplacl:GroupCondition/oplacl:hasQuery) andacl:Authorization(needsacl:accessTo,oplacl:hasAccessMode oplacl:Read,oplacl:hasRealm oplacl:DefaultRealm,oplacl:hasScope oplacl:PrivateGraphs,acl:agentpointing at the group,wdrs:describedby) are both independently gate-enforced as structural shapes.- The prior-art reference file also includes an additional
filter (sql:stripe_customer_has_active_product_subscription(...))clause for Stripe-backed recurring subscriptions — include it when the offer is a recurring subscription (see Subscription Pricing above), matching the style ofoplacl:demo-graph-access-world-cup-meshup's group condition. - This authorization block is currently required for Graph Access offers only (per explicit scope decision) — File Access and API Access offers don't need it yet.
API Access Product (WebAPI) Description Template + 4-Tier Offer Structure
Unlike File/Graph Access (one specific resource per offer), an API Access offer file describes ONE shared OPAL Chat API Product per host, sold as exactly 4 separate Offer/License/PriceSpecification triples — Entry (free), Medium ($9.99/mo), Advanced ($19.99/mo), Max ($49.99/mo) — all sharing the same Product. This matches the two dedicated prior-art files (OPALOffers-Licenses-Prices-ODS-QA-API.ttl, OPALOffers-Licenses-Prices-URIBurner-API.ttl). There is no user-supplied "resource identifier" the way File/Graph Access needs one — the tier names themselves (ApiAccessEntryLevel / ApiAccessMediumLevel / ApiAccessAdvancedLevel / ApiAccessMax) ARE the {OfferIdentifier} for each of the 4 offers.
Product template (one per host, shared across all 4 tiers):
<http://data.openlinksw.com/oplweb/{host_short}OPAL-API#this> a schema:WebAPI ;
schema:name "OPAL Chat API via {host_short}"@en ;
schema:description """API endpoint for interacting with OPAL on {host_short}, enabling programmatic Machine-Centric Programming (MCP) chat sessions."""@en ;
schema:serviceType "MCP-compliant AI Chat Agent API"@en ;
schema:url <https://{host_hostname}/chat/api> ;
skos:related <http://data.openlinksw.com/oplweb/{host_short}OPAL#this> ;
schema:provider [
a schema:Organization ;
schema:name "OpenLink Software"@en ;
schema:url <https://www.openlinksw.com/> ;
] ;
schema:documentation <https://opal.openlinksw.com/install/> ;
schema:exampleOfWork <https://www.openlinksw.com/data//html/opal-system-functions-or-agents-apis-developer-journey-gemini-17.html> ;
schema:hasPart [
a schema:EntryPoint ;
schema:name "OPAL Chat Endpoint" ;
schema:urlTemplate "https://{host_hostname}/chat/api" ;
schema:httpMethod "POST" ;
schema:contentType "application/json" ;
schema:actionPlatform <http://schema.org/OnlinePlatform>
] ;
schema:potentialAction [
a schema:CommunicateAction ;
schema:name "SendMessage" ;
schema:target [
a schema:EntryPoint ;
schema:urlTemplate "https://{host_hostname}/chat/api" ;
schema:httpMethod "POST"
] ;
schema:expects [ a schema:Text ; schema:description "JSON payload containing message content and metadata for OPAL" ] ;
schema:result [ a schema:Text ; schema:description "JSON response containing the OPAL chat reply or status" ]
] .Each of the 4 tiers is its own Offer/License/PriceSpecification, e.g., for Entry:
<http://data.openlinksw.com/oplweb/offer/ApiAccessEntryLevelOffer{host_suffix}#this>
a schema:Offer, oplofr:SubscriptionOffer, oplofr:ApiAccessEntryLevelOffer ;
schema:itemOffered <http://data.openlinksw.com/oplweb/license/ApiAccessEntryLevelLicense{host_suffix}#this> ;
schema:priceSpecification <http://data.openlinksw.com/oplweb/offer-unitprice/ApiAccessEntryLevelPriceSpecification#this> ;
schema:url <https://{host_hostname}/chat/api> ;
skos:prefLabel "OPAL API Access on {host_short} — Entry Level (BYOK)" ;
... (offerNumber, name, comment, description, businessFunction gr:Sell, isMemberOf OfferGroupApiAccess, validFrom/Through, potentialAction cart URL, wdrs:describedby — same as any other Offer) .
<http://data.openlinksw.com/oplweb/offer-unitprice/ApiAccessEntryLevelPriceSpecification#this>
a schema:UnitPriceSpecification ;
schema:priceCurrency "USD"^^xsd:string ;
schema:price "0.00"^^xsd:decimal ;
... (validFrom/Through, wdrs:describedby — NO oplofr:interval/intervalCount, since Entry is free) .
<http://data.openlinksw.com/oplweb/license/ApiAccessEntryLevelLicense{host_suffix}#this>
a opllic:ProductLicense, opllic:Product, opllic:ACLOnly, opllic:SubscriptionLicense ;
opllic:productLicenseOf <http://data.openlinksw.com/oplweb/{host_short}OPAL-API#this> ;
skos:related <https://{host_hostname}/oauth/applications.vsp> ;
... (name, description, prefLabel, image, partOf, hasDuration, OS/DB family, wdrs:describedby — same as any other License; NO opllic:uriParameter/graphParameter for API Access) .The other 3 tiers (Medium $9.99, Advanced $19.99, Max $49.99) follow the identical structure with oplofr:interval "month"^^xsd:string + oplofr:intervalCount "1"^^xsd:integer added to their PriceSpecification (required for any non-$0.00 price — the gate enforces this pairing via ApiAccessPaidPriceShape's sh:or).
Notes:
schema:imageon the License: prefer<https://www.openlinksw.com/DAV/oplweb3/images/opal-license.png>(the API-Access-specific convention from both prior-art files) over the generic fallback image.schema:documentationandschema:exampleOfWorkare fixed, host-independent values (identical across both prior-art files) — the gate enforces them as exact matches.skos:relatedis required on both the Product (→ the host's OPAL server Product, same as File/Graph) and the License (→ the host's OAuth applications page,https://{host_hostname}/oauth/applications.vsp) — these are two different things despite the same predicate.- Every Offer needs
skos:prefLabel(gate-enforced for API Access specifically — this isn't required on Offers generally, only on Licenses). - The tier types (
oplofr:ApiAccessEntryLevelOfferetc.) already satisfy the universal "resource-specific offer type" rule on their own — no separate generic marker class is needed (unlike File/Graph'sDemoFileAccessOffer/DemoGraphAccessOffer+ resource-specific-type combo). - Bug fixed while building this out: the pre-existing
ApiAccessPaidPriceShapeunconditionally requiredoplofr:interval/intervalCounton every API Access price, which would have wrongly rejected the free Entry tier. It's now conditional (sh:or: either price is exactly"0.00"^^xsd:decimal, or interval+intervalCount are present) — also fixed the interval value itself from a bare"month"to"month"^^xsd:stringto match this skill's own established literal-typing convention (see priceCurrency rule below), which the bare form silently failed to match. - 4-tier completeness gate:
ApiAccessProductShapeincludes ash:sparqlconstraint requiring that, for the shared Product, all 4 tiers exist — a License withopllic:productLicenseOfthis Product whoseopllic:partOf-linked Offer is typedoplofr:ApiAccessEntryLevelOffer, another foroplofr:ApiAccessMediumLevelOffer, another foroplofr:ApiAccessAdvancedLevelOffer, another foroplofr:ApiAccessMaxOffer. Generating only some of the 4 tiers now fails the gate. Note on SHACL-SPARQL:VALUESclauses are disallowed insidesh:sparqlconstraints per the SHACL spec (confirmed via pyshacl: "A SPARQL Constraint must not contain a VALUES clause") — the check is written as aUNIONof 4 separateFILTER NOT EXISTSbranches instead. Checking by exact tier type (not price point) was a deliberate choice — the tier's identity is what matters, and prices are a business detail that could reasonably change later without affecting the invariant "all 4 tiers exist."
Chat Service Product/License/Offer Description Template
Unlike the other three types, a Chat Service offer is NOT tied to a specific file/graph/API-endpoint resource — it sells interactive use of the OPAL AI agent platform itself (module-specific assistants, or generic Bring-Your-Own-Key/system-wide tiers). There is no "License resource IRI" input for this type. Structurally it is the most open-ended of the four: unlike API Access's rigid single-Product/4-fixed-tiers bundle, Chat Service can have an arbitrary number of independent offer families (e.g., Data Twingler, Support Assistant, generic BYOK/Max), each typically at 4 tiers (Entry/Plus/Pro/Max), all sharing ONE Product per host. Product template (one per host, shared across every family/tier):
<http://data.openlinksw.com/oplweb/{host_short}OPAL#this> a schema:Service ;
schema:name "OPAL via {host_short}"@en ;
schema:applicationCategory "AI Agent Platform"@en ;
schema:applicationSubCategory "LLM-Integrated AI Agent Middleware"@en ;
schema:description """OPAL (OpenLink AI Layer) instance hosted by {host_short}, enabling AI Agent interaction with Data Spaces using natural language and the MCP protocol."""@en ;
schema:softwareVersion "1.0"@en ;
schema:provider [ a schema:Organization ; schema:name "OpenLink Software"@en ; schema:url <https://www.openlinksw.com/> ] ;
schema:url <https://{host_hostname}/> ;
schema:hasPart
[ a schema:WebAPI ; schema:name "OPAL Chat Endpoint"@en ; schema:description "..."@en ; schema:serviceType "MCP-compliant AI Chat Agent"@en ; schema:url <https://{host_hostname}/chat> ; schema:documentation <https://opal.openlinksw.com/install/> ; schema:termsOfService <https://opal.openlinksw.com/tos/> ],
[ a schema:WebAPI ; schema:name "OPAL Assist-Metal Endpoint"@en ; schema:description "..."@en ; schema:serviceType "MCP-compliant AI Assist Agent"@en ; schema:url <https://{host_hostname}/assist-metal> ; schema:documentation <https://opal.openlinksw.com/install/> ; schema:termsOfService <https://opal.openlinksw.com/tos/> ],
[ a schema:WebAPI ; schema:name "MCP Server (Streamable HTTP)"@en ; schema:description "..."@en ; schema:serviceType "MCP Server (Streamable HTTP)"@en ; schema:url <https://{host_hostname}/chat/mcp/messages> ; schema:documentation <https://opal.openlinksw.com/install/> ; schema:termsOfService <https://opal.openlinksw.com/tos/> ],
[ a schema:WebAPI ; schema:name "MCP Server (Server Sent Events)"@en ; schema:description "..."@en ; schema:serviceType "MCP Server (SSE)"@en ; schema:url <https://{host_hostname}/chat/mcp/sse> ; schema:documentation <https://opal.openlinksw.com/install/> ; schema:termsOfService <https://opal.openlinksw.com/tos/> ] .This is the same shared {host_short}OPAL#this IRI already documented in references/offer-iri-patterns.md as the OPAL ACL server referenced via skos:related from File/Graph/API Access Products — here it plays its primary role, as the licensed Product itself.
Each tier (of each family) is a standard Offer/License/PriceSpecification triple with oplofr:isMemberOf <http://data.openlinksw.com/oplweb/OfferGroupChatService#this>, opllic:productLicenseOf pointing at the shared {host_short}OPAL#this node, and its own resource-specific oplofr:{OfferIdentifier}Offer type (e.g., oplofr:DataTwinglerSpecificModuleEntryLevelOffer, oplofr:BringYourOwnKeyPlusOffer, oplofr:MaxOffer). Unlike API Access, the Offer does NOT carry schema:url directly (only the Product/its hasPart sub-resources do).
Notes:
schema:hasPartrequires (gate-enforced) all 4 named sub-endpoints present — Chat, Assist-Metal, MCP Streamable HTTP, MCP SSE — confirmed identical across both prior-art host files (only the URL host differs). Usessh:qualifiedValueShape/sh:qualifiedMinCount(one clause per requiredserviceType) rather than a rigidhasPartcount, so adding a 5th endpoint later won't break the shape.schema:imageon the License: sameopal-license.pngconvention as API Access.- License does NOT need
skos:related(unlike API Access's OAuth-applications-page requirement) — Chat Service licenses in prior art don't carry it. - Every Offer needs
skos:prefLabel(same as API Access — not required on Offers generally). - No completeness gate for the multi-family structure (deliberately, this round): unlike API Access's fixed single bundle, Chat Service's open-ended family/tier structure doesn't map cleanly onto a fixed exclusion-list or fixed-class SPARQL check. If a future request wants "all 4 tiers of a given family must exist," that's a natural follow-up, matching how Graph Access's authorization block and API Access's completeness gate were each separately requested after their initial template work.
- Bugs fixed in the underlying prior-art file while building this out (same due-diligence pass as the File/Graph/API Access work): one License (
BringYourOwnKeyProLicenseOds-qa) was missingopllic:partOfentirely; all 12 offers'schema:potentialActioncart URLs were missing the/shop/path segment (shop.openlinksw.com/cart.vspinstead ofshop.openlinksw.com/shop/cart.vsp) — both fixed when regenerating from this template. - Validation note:
scripts/validate-offers-shacl.py --type chatservice(or bare auto-detect) now validates againstchat-service-offer-shape.ttl+common-offer-shape.ttl.
Auto-Derivation
- name: First sentence ≤80 chars
- pref_label: Abbreviated ≤60 chars
- comment: "Purchasing this offer grants {description}"
- description: The Offer node MUST also carry its own
schema:description(distinct fromschema:commentabove) — required, e.g., "{Offer type} offer providing {description}." Omitting it is not just a SHACL gate failure:OPLSHOP.DBA.offers_sparql_base()in the live shop'sopl_shop_offer_ui.sqlrequiresschema:descriptionas a non-optional triple in its main catalog listing query — an Offer without it is silently excluded from every offer listing in the shop, even though nothing else about the file is wrong. - itemOffered:
schema:itemOfferedon the Offer MUST point at the License IRI (theopllic:ProductLicensenode), never at the Product (theschema:WebAPInode). The shop's detail-population queries (get_offer_info,get_offer_dbms_info,get_subscription_info,get_eval_action,get_service_uriinopl_shop_offer_ui.sql) all do?offer schema:itemOffered ?licand then read License-only properties off?lic(schema:image,oplsof:hasOperatingSystemFamily,opllic:productLicenseOf,opllic:hasDuration, ...). PointingitemOfferedat the Product instead makes every one of those AJAX calls return empty, breaking the offer card's detail rendering even if the offer otherwise appears in the catalog list. - duration: If price is a recurring monthly price ("per month"),
opllic:hasDurationMUST be the canonical<http://data.openlinksw.com/oplweb/license/License-Duration#ongoing-subscription>IRI (seereferences/offer-iri-patterns.md) — never invent/type a local Duration node. - duration (one-time purchase): If the price is a one-time, non-recurring charge,
opllic:hasDurationMUST be the canonical<http://data.openlinksw.com/oplweb/license/License-Duration#perpetual>IRI — also never invent/type a local Duration node. Before using any Duration IRI not already listed inreferences/offer-iri-patterns.md, query the live store for existingopllic:Durationinstances (SELECT ?d ?name WHERE { GRAPH ?g { ?d a opllic:Duration . OPTIONAL { ?d schema:name ?name } } }) rather than assuming one exists or minting a fresh one — added 2026-09-08 after confirming only#ongoing-subscriptionand#annualexisted live at the time. - potentialAction:
schema:potentialActionon the Offer MUST always be a direct "add to cart" IRI on the production shop, regardless of which host platform profile the licensed resource itself lives on (URIBurner, ODS-QA, Localhost — the cart is alwaysshop.openlinksw.com):https://shop.openlinksw.com/shop/cart.vsp?command=add&item={percent-encoded Offer IRI}. Percent-encode the full Offer IRI (:→%3A,/→%2F,#→%23) as theitemvalue. Never emit a blank-nodeschema:Action/EntryPointstructure — the shop's listing query readsschema:potentialActionas a flat triple and expects the value itself to be the clickable cart URL. - subscription pricing: Whenever the input price is phrased as "per
<unit>" (per month, per year, ...) — i.e., it recurs on a billing cadence rather than being a one-time charge — the PriceSpecification MUST carryoplofr:interval "<unit>"^^xsd:string(e.g.,"month") andoplofr:intervalCount "<n>"^^xsd:integer(e.g.,1), and the Offer MUST carry an additionalrdf:type oplofr:SubscriptionOfferalongsideschema:Offerand the offer-type-specific classes (oplofr:DAVOffer/oplofr:DemoFileAccessOfferetc.). One-time, non-recurring prices carry neitheroplofr:intervalnor theSubscriptionOffertype. The SHACL gate enforces the pairing both ways: a PriceSpecification withoplofr:intervalbut nooplofr:intervalCountfails, and an Offer whose price hasoplofr:intervalbut isn't typedoplofr:SubscriptionOfferfails. - validity: Unless the user gives an explicit validity window, default
schema:validFromon both the Offer and its PriceSpecification to the file's creation date/time (the same instant used for thesource:document'sschema:datePublished), and defaultschema:validThroughto exactly 3 months after that. Keep the Offer's and the PriceSpecification's validity window identical to each other unless told otherwise. - image: The License MUST carry
schema:image. If the user doesn't specify a particular image for this offer, default to<https://www.openlinksw.com/DAV/oplweb3/images/controlled-access-to-data-assets.jpg>. - license typing: Every License (File Access, Graph Access, or API Access — all three) MUST always be typed
opllic:ProductLicense, opllic:Product, opllic:ACLOnly.opllic:SubscriptionLicenseis a fourth, conditional type: include it if and only if the License's own Offer is typedoplofr:SubscriptionOffer(i.e., a genuinely recurring price — see the "subscription pricing" rule below). A one-time, non-recurring offer's License must NOT carryopllic:SubscriptionLicense. Bug found and fixed 2026-09-10: this rule previously said SubscriptionLicense was required "regardless of whether the price is one-time or recurring" — that was wrong, and it caused every one-time File Access offer generated under the old rule to be mistyped as a subscription license. That mistyping is also the confirmed root cause of a live ods-qa payment-completion bug: the shop's Stripe-SPT completion procedures branch on the License'srdf:typeto decide which settlement path to use, so a one-time License wrongly carryingSubscriptionLicensegets routed into subscription-only settlement logic and fails to complete. The SHACL gate now enforces the corrected, conditional rule via ash:sparqlconstraint onBaseLicenseShapecomparing the Offer'soplofr:SubscriptionOffertyping against the License'sopllic:SubscriptionLicensetyping — the two must always agree. - priceCurrency literal form:
schema:priceCurrencyMUST always be written with an explicit^^xsd:stringdatatype annotation —schema:priceCurrency "USD"^^xsd:string ;— never the bare/plain form (schema:priceCurrency "USD" ;). This is a serialization convention only, not something the SHACL gate can check: a plain string and an explicitly-typed"USD"^^xsd:stringare the same RDF term under RDF 1.1 simple-literal semantics, sosh:datatype xsd:stringalready accepts both and can't tell them apart. Get it right at generation time. - schema.org prefix name: the bound prefix for
http://schema.org/MUST beschema:— neverschema1:. Found 2026-09-08: the two live prior-art reference files (whatever process originally wrote them) useschema1:, an artifact of their own serialization, not a naming convention to imitate — SKILL.md's own templates above have always usedschema:. Regenerating or editing an offer file that currently has@prefix schema1:should rename it toschema:throughout (a safe global find/replace, sinceschema1never appears as a substring of anything else in these files). - language tags on prose literals: every natural-language string value on
schema:name,schema:description,schema:comment,skos:prefLabel,skos:altLabel,foaf:name, andcc:attributionNameMUST carry@en— e.g.,schema:name "Food Bookmark Collection (RDF-Turtle) — File Access on URIBurner"@en. This applies everywhere those predicates appear with free-text prose: on the Offer, License, PriceSpecification, the shared Product/WebAPI blocks (already@en-tagged in prior art — keep as is),acl:Authorization'sschema:name, andoplacl:ConditionalGroup'sfoaf:name. Two required exceptions, both because the SHACL gate pins the datatype: thesource:document's ownschema:name(DocumentShaperequiressh:datatype xsd:string) and theopllic:Durationnode'sschema:name(DurationShaperequires the same) must stay untagged plain/xsd:stringliterals — adding@enthere gives the literalrdf:langStringdatatype instead ofxsd:stringand fails the gate. Never add a language tag toschema:price(xsd:decimal),schema:priceCurrency/schema:unitCode/oplofr:interval/oplofr:offerNumber(xsd:stringcodes, not prose),oplofr:intervalCount(xsd:integer), or anyxsd:dateTimevalue — language tags belong on natural-language text, not codes, numbers, or dates. - resource-specific offer type: Every Offer (all three offer types) MUST carry an additional
rdf:typein theoplofr:namespace namedoplofr:{OfferIdentifier}Offer— specific to the resource being sold, not to the host serving it.{OfferIdentifier}is the exact same PascalCase identifier used in the Offer/License/PriceSpecification IRIs (see Entity IRI Naming Convention) — no{host_suffix}. Example: for the Offer at.../offer/JchTestGraphOfferOds-qa#this(selling access tourn:jch:test), addoplofr:JchTestGraphOfferalongsideschema:Offer,oplofr:DemoGraphAccessOffer,oplofr:SubscriptionOffer. The SHACL gate enforces this onBaseOfferShapevia a qualified value shape: at least onerdf:typevalue must be anoplofr:IRI ending inOfferthat is NOT one of the known host-generic classes (oplofr:DAVOffer,oplofr:DemoFileAccessOffer,oplofr:DemoGraphAccessOffer,oplofr:SubscriptionOffer) — if a new host-generic offer class is ever added (e.g., for API Access), add it to that exclusion list inshacl/common-offer-shape.ttl'sBaseOfferShape, or a real resource-specific offer would incorrectly satisfy the check by accident, or worse, a missing resource-specific type could go undetected. - offer-level product family: Every Offer (all four offer types) MUST carry its own
skos:related— separate from, and in addition to, anyskos:relatedalready required on the Product/License — whose object is a blank node with exactly oneoplpro:hasFamilytriple pointing at a fixed product-family IRI, chosen by whichoplofr:isMemberOfOfferGroup the offer belongs to:OfferGroupFileAccessorOfferGroupGraphAccess→skos:related [ oplpro:hasFamily <http://data.openlinksw.com/oplweb/product_family/loac#this> ];OfferGroupApiAccessorOfferGroupChatService→skos:related [ oplpro:hasFamily <http://data.openlinksw.com/oplweb/product_family/opal#this> ]. This is confirmed by every current prior-art file across all four offer types. The SHACL gate enforces it per type on each*OfferShapewithsh:nodeKind sh:BlankNode(a named-IRI object, the shape otherwise used for Product/Licenseskos:related, now fails here) plus a nestedsh:hasValuecheck onoplpro:hasFamilyfor the type-appropriate family IRI.
GATE: 0 FAIL
python3 scripts/validate-offers-shacl.py output.ttl --type {file|graph|api|chatservice} — must pass before delivery.
Server-Side Generation (ProtectedOffer DET)
Virtuoso's ProtectedOffer DAV DET applies the File Access contract automatically on upload, then notifies the instance admin so the offer can be loaded into each shop. See references/det-integration.md.
Known gap: the three prompts/*-offer-prompt.md files are empty (0 bytes). Until they are written, SKILL.md and shacl/ are the operative contract.
Loading into Shop
SPARQL define get:soft "no-sponge" LOAD <file:///path/to/output.ttl> INTO <urn:opl:shop:offering:sponging:cache:official> ;License
AGPL-3.0
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

