Solid/RDF 数据建模专家,处理 Pod 数据结构设计、类继承、属性定义、命名空间等问题
Use this skill when changing or reviewing Solid Pod data models, RDF vocab mappings, drizzle-solid schemas, repositories, or tests that assert cross-app data semantics.
Model the Pod as a Linked Data graph, not as a relational database with foreign keys.
id values as base-relative resource ids when the storage path
is part of identity; do not collapse them to fragment/local ids.xxxId secretly behave like an RDF link.chat, thread, message, maker, replyTo, workspace.When a project has a shared model/ORM package, that package owns durable Pod storage semantics.
Do not hand-parse shared business TTL when an ORM schema/resource exists.
SolidDatabase/ORM client, not only a
raw fetch. A fetch-only boundary often forces callers into listing Pod
containers, reading .ttl documents, and parsing triples manually.db.select().from(resource).where(...) for list/subset reads.db.findById, db.updateById, and db.deleteById when callers hold a
canonical base-relative resource id.db.findByIri, db.updateByIri, and db.deleteByIri when the concrete
resource IRI is known.db.findByResource, db.updateByResource, and db.deleteByResource
only at adapter boundaries that intentionally accept mixed exact targets such
as a full IRI, base-relative resource id, or row.findByLocator,
updateByLocator, and deleteByLocator are legacy compatibility surfaces;
new model, repository, and app code should use ById, ByIri, or a repository
helper with domain-shaped parameters.{yyyy}/{MM}/{dd}.ttl#{id}, a fragment/local id is not a complete resource
id. Resolve it through a repository/index first, or pass the canonical
base-relative resource id that includes every storage slot.fetch access is acceptable only for protocol-level
adapters where no shared business resource exists yet, and that boundary
should be isolated and marked as compatibility/protocol code.Schema/repository storage semantics and app ergonomics are different layers.
Model a Pod document path as the resource identity when the document is the thing being described.
For RDF documents such as .ttl, the file can describe itself. In that case:
base is the container/base IRI prefix where resources live.subjectTemplate should produce the document-relative resource IRI.path, filePath, wikiPath, or similar fields when the URI
already encodes identity and location..meta only when the primary resource cannot describe itself, for example
binary files, non-RDF files, or external metadata about a separate resource.Preferred one-resource-per-file pattern:
base: '/settings/autonomy/grants/'
subjectTemplate: '{id}.ttl'
This models:
</settings/autonomy/grants/grant-1.ttl>
dcterms:title "Allow safe file search" ;
dcterms:conformsTo </settings/autonomy/schema/grant.ttl#GrantWikiPage> .
Avoid adding a separate metadata subject for the same TTL page:
</settings/autonomy/grants/grant-1.ttl#meta> ...
Use a fragment subject only when the document contains multiple first-class resources, for example:
base: '/chat/{chatId}/2026/05/'
subjectTemplate: '07.ttl#{id}'
For append-heavy event logs, date-bucketed fragment resources are often better than one file per event. For durable page-like resources that users or agents read as a document, one TTL document per page is usually clearer.
When mapping an LLM Wiki-style page model to Solid:
schema to dcterms:conformsTo <shape-or-schema-uri>..meta for TTL wiki pages unless describing a different
non-RDF payload.Example:
</settings/autonomy/grants/grant-1.ttl>
a <https://undefineds.co/ns#GrantWikiPage> ;
dcterms:title "Allow safe repository inspection" ;
dcterms:abstract "Permit bounded read-only commands in trusted workspaces." ;
dcterms:conformsTo </settings/autonomy/schema/grant.ttl#GrantWikiPage> .
Index fields may exist for filtering, but they are not a substitute for semantic evaluation. For example, approval grants can expose action/risk/target metadata for lookup while the AI Secretary still reads the page body, summary, tags, provenance, and context to decide whether a concrete request is covered.
Use full URI relation fields:
message.chat // full Chat IRI
message.thread // full Thread IRI
message.replyTo // full Message IRI, if present
thread.chat // full Chat IRI
Avoid relation fields like:
message.chatId
message.threadId
thread.chatId
unless they are intentionally opaque local strings and are not RDF links.
It is fine, and usually preferable, to expose short-id helpers:
messageRepository.listByThreadId(db, { chatId, threadId })
threadRepository.listByChatId(db, { chatId })
These helpers should convert short ids to canonical Pod IRIs or canonical base-relative resource ids internally before querying URI fields.
Do not force UI/CLI callers to manually construct Pod IRIs in every where clause.
Do not model a relation as threadId.link(threadResource) when the target
resource subject template needs more than id, for example:
thread.subjectTemplate = '{chatId}/index.ttl#{id}'
A single threadId cannot safely resolve that IRI because chatId is also required. This can produce template residue such as:
/.data/chat/{chatId}/index.ttl#thread-1
Preferred fixes:
message.thread.{ chatId, threadId } and derives the full URI.For Solid Chat-like models, align with graph semantics:
| Concept | Preferred RDF direction | Typical predicate |
|---|---|---|
| chat contains message | chat -> message |
meeting:message / wf:message as project vocabulary dictates |
| thread contains message | thread -> message |
sioc:has_member |
| reply points to original | replyMessage -> originalMessage |
sioc:has_reply or project reply predicate |
| author/maker | message -> maker |
foaf:maker |
Therefore, a message can be connected to both chat and thread. In a row shape this may surface as message.chat and message.thread, but the RDF triples may be inverse links:
<chat> meeting:message <message> .
<thread> sioc:has_member <message> .
<reply> sioc:has_reply <original> .
<message> foaf:maker <maker> .
When the ORM supports inverse predicates, use them for read/write symmetry. If inverse predicates are not robust enough for the operation, write relation triples explicitly in repository code and cover with integration tests.
id: the resource id relative to the model base. For one-resource-per-file
templates this may look local, but for compound/date-bucketed templates it
must include the path and fragment needed to identify the resource, such as
chat/default/2026/05/18/runs.ttl#run_x.id.default((key) => ...): the preferred field-level default generator for
compound/date-bucketed resource ids. key is the ORM-generated local random
key; it is not a schema field or business concept. If callers provide id
explicitly, they must provide the same complete base-relative resource id
shape that the default would have produced.subjectTemplate: keep it, but narrow it to expanding the complete id into
the subject. The preferred final shape is subjectTemplate: '{id}' or an ORM
default equivalent. Do not put {key} in subjectTemplate; key only belongs
to the id.default((key) => ...) generator.subjectTemplate variable such
as {id}; do not return it as the durable id when the resource lives in a
compound path.chat, thread, message, replyTo, maker: URI-valued graph relations.chatId, threadId, messageId: acceptable as function parameters, UI selection state, runtime protocol fields, or derived aliases; avoid as persisted RDF link fields.Uri suffix may be used for compatibility adapters, but prefer semantic names in new Pod schemas.For schema changes, verify both ergonomics and graph semantics:
Do not preserve obsolete tests that encode abandoned storage paths. Retire or rewrite them around the current Pod/xpod product path.
Product-specific domain rules do not belong in this skill file.
chat, thread, message, or session, the
package that owns those schemas should be the single source of truth.