SKILL.md
Solid Modeling
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.
Core stance
Model the Pod as a Linked Data graph, not as a relational database with foreign keys.
- Use RDF/Pod relation fields for URI references.
- Use local ids only for subject-template variables, UI labels, runtime keys, or
repository/API ergonomics.
- Treat durable
idvalues as base-relative resource ids when the storage path
is part of identity; do not collapse them to fragment/local ids.
- Do not let a field named
xxxIdsecretly behave like an RDF link. - Prefer explicit semantic field names:
chat,thread,message,maker,replyTo,workspace. - UI-only state stays outside Pod. Durable shared state belongs in Pod.
Shared model ownership
When a project has a shared model/ORM package, that package owns durable Pod storage semantics.
- If a resource/schema/repository already exists in the shared model package,
app shells should use it directly.
- Do not copy shared predicates, subject templates, Turtle serializers, URI
builders, or resource state machines into CLI, web, mobile, or plugin shells.
- If the shell needs a missing query or mutation, add the repository/helper and
contract tests to the shared model package first, then call it from the shell.
- Shell-specific code may keep only interaction and protocol adaptation: CLI/TUI
rendering, GUI state, command flags, local cache plumbing, and mapping native runtime events into shared insert/update DTOs.
ORM-first Pod access
Do not hand-parse shared business TTL when an ORM schema/resource exists.
- Default runtime stores should receive a
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.
- Use
db.select().from(resource).where(...)for list/subset reads. - Use
db.findById,db.updateById, anddb.deleteByIdwhen callers hold a
canonical base-relative resource id.
- Use
db.findByIri,db.updateByIri, anddb.deleteByIriwhen the concrete
resource IRI is known.
- Use
db.findByResource,db.updateByResource, anddb.deleteByResource
only at adapter boundaries that intentionally accept mixed exact targets such as a full IRI, base-relative resource id, or row.
- Do not expose locator-shaped inputs in business APIs.
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.
- For compound or date-bucketed templates such as
{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.
- If a shell cannot express a needed query/mutation without parsing TTL, add the
missing resource/repository API to the shared model package instead of moving a Turtle parser into the model package.
- Raw Solid client or
fetchaccess 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.
URI fields vs short-id query ergonomics
Schema/repository storage semantics and app ergonomics are different layers.
Document resources, base paths, and subjects
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:
baseis the container/base IRI prefix where resources live.subjectTemplateshould produce the document-relative resource IRI.- The RDF subject is usually the document URI itself, not a synthetic fragment.
- Do not store
path,filePath,wikiPath, or similar fields when the URI
already encodes identity and location.
- Use
.metaonly 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.
LLM Wiki page mapping
When mapping an LLM Wiki-style page model to Solid:
- The wiki page is a Pod resource URI, normally a TTL document URI.
- Page attributes such as title, summary, body, tags, source, provenance, and
compiled timestamps are predicates on that page URI.
- The wiki schema/shape is a URI relation, not a filesystem path string.
- Map
schematodcterms:conformsTo <shape-or-schema-uri>. - Do not introduce
.metafor 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.
Pod schema layer
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.
Repository/API layer
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.
Avoid single-field links to compound subject templates
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:
- Store a full URI relation field, e.g.
message.thread. - Use a repository helper that accepts
{ chatId, threadId }and derives the full URI. - If the RDF relation direction should be inverse, use an inverse column or explicit relation writer rather than pretending the child owns the forward predicate.
Solid Chat relationship guidance
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.
Naming rules
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 completeidinto
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.
- local/template id: the value inserted into a
subjectTemplatevariable 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.Urisuffix may be used for compatibility adapters, but prefer semantic names in new Pod schemas.
Testing expectations
For schema changes, verify both ergonomics and graph semantics:
- Unit tests for URI builders and short-id repository helpers.
- Local xpod integration tests for write/readback.
- Strict seed/readback tests when changing relation semantics:
- chat - thread - message - session - approval/consent/grant/audit where relevant
- Assert no unresolved template placeholders remain in produced IRIs.
Do not preserve obsolete tests that encode abandoned storage paths. Retire or rewrite them around the current Pod/xpod product path.
Product semantics boundary
Product-specific domain rules do not belong in this skill file.
- Put product data semantics in the owning package schemas, repositories, and shared docs.
- Keep this skill limited to reusable Solid/RDF modeling guidance.
- If a product defines terms such as
chat,thread,message, orsession, the
package that owns those schemas should be the single source of truth.