feat(ai-workspace): support SEO codex provider bridge
This commit is contained in:
@@ -36,6 +36,7 @@ catalog/context-bindings.json
|
||||
catalog/assistant-access-policy.json
|
||||
catalog/assistant-actions.json
|
||||
catalog/assistant-risk-policy.json
|
||||
catalog/domain-packages/*/*.json
|
||||
examples/*.json
|
||||
docs/baseline/*.md
|
||||
docs/CONTEXT_BINDINGS.md
|
||||
@@ -43,6 +44,8 @@ docs/ASSISTANT_ACCESS_POLICY.md
|
||||
docs/ASSISTANT_ACTION_REGISTRY.md
|
||||
docs/ASSISTANT_CALLER_CONTRACT.md
|
||||
docs/ASSISTANT_EXECUTION_GATEWAY.md
|
||||
docs/SEO_DOMAIN_ONTOLOGY.md
|
||||
docs/PROJECT_ONTOLOGY_INSTANCE_SCHEMA.md
|
||||
src/catalog.mjs
|
||||
src/registry.mjs
|
||||
src/validate.mjs
|
||||
@@ -144,6 +147,22 @@ The resolver returns:
|
||||
- existing context bindings;
|
||||
- missing binding types required for the selected route.
|
||||
|
||||
## Domain Packages
|
||||
|
||||
Ontology Core can load domain packages from:
|
||||
|
||||
```text
|
||||
catalog/domain-packages/*
|
||||
```
|
||||
|
||||
Each package may contribute entities, relations, aliases, guardrails, evidence, resolver rules, context binding types, assistant access policy entries, assistant actions, and risk policy entries.
|
||||
|
||||
Current package:
|
||||
|
||||
- `seo` - NDC SEO mod domain ontology for site scans, project ontology instances, scope contracts, semantic analysis, market evidence, optimization planning, validation, changesets, and future app-owned SEO assistant actions.
|
||||
|
||||
The package loader merges domain packages into the base catalog before validation. Domain packages extend core meanings; they do not own app data or execute mutations.
|
||||
|
||||
## First Implementation Target
|
||||
|
||||
The first useful resolver flows are:
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"updatedAt": "2026-07-02",
|
||||
"aliases": [
|
||||
{ "alias": "SEO mod", "canonicalId": "seo.project" },
|
||||
{ "alias": "NDC SEO mod", "canonicalId": "seo.project" },
|
||||
{ "alias": "seo project", "canonicalId": "seo.project" },
|
||||
{ "alias": "site", "canonicalId": "seo.site" },
|
||||
{ "alias": "сайт", "canonicalId": "seo.site" },
|
||||
{ "alias": "source", "canonicalId": "seo.source" },
|
||||
{ "alias": "scan", "canonicalId": "seo.scan_version" },
|
||||
{ "alias": "scope", "canonicalId": "seo.scope_contract" },
|
||||
{ "alias": "scope contract", "canonicalId": "seo.scope_contract" },
|
||||
{ "alias": "page", "canonicalId": "seo.page" },
|
||||
{ "alias": "страница", "canonicalId": "seo.page" },
|
||||
{ "alias": "section", "canonicalId": "seo.section" },
|
||||
{ "alias": "block", "canonicalId": "seo.section" },
|
||||
{ "alias": "секция", "canonicalId": "seo.section" },
|
||||
{ "alias": "блок", "canonicalId": "seo.section" },
|
||||
{ "alias": "ProjectOntology", "canonicalId": "seo.project_ontology" },
|
||||
{ "alias": "project ontology", "canonicalId": "seo.project_ontology" },
|
||||
{ "alias": "semantic profile", "canonicalId": "seo.semantic_profile" },
|
||||
{ "alias": "intent cluster", "canonicalId": "seo.intent_cluster" },
|
||||
{ "alias": "demand universe", "canonicalId": "seo.demand_universe" },
|
||||
{ "alias": "semantic anchor", "canonicalId": "seo.semantic_anchor" },
|
||||
{ "alias": "смысловой якорь", "canonicalId": "seo.semantic_anchor" },
|
||||
{ "alias": "anchor review", "canonicalId": "seo.anchor_review" },
|
||||
{ "alias": "фиксация ключей", "canonicalId": "seo.anchor_review" },
|
||||
{ "alias": "wordstat queue", "canonicalId": "seo.wordstat_queue" },
|
||||
{ "alias": "очередь Wordstat", "canonicalId": "seo.wordstat_queue" },
|
||||
{ "alias": "seed", "canonicalId": "seo.seed_query" },
|
||||
{ "alias": "seed query", "canonicalId": "seo.seed_query" },
|
||||
{ "alias": "keyword", "canonicalId": "seo.keyword" },
|
||||
{ "alias": "ключ", "canonicalId": "seo.keyword" },
|
||||
{ "alias": "ключевик", "canonicalId": "seo.keyword" },
|
||||
{ "alias": "Wordstat signal", "canonicalId": "seo.market_signal" },
|
||||
{ "alias": "market evidence", "canonicalId": "seo.market_signal" },
|
||||
{ "alias": "keyword curation board", "canonicalId": "seo.keyword_curation_board" },
|
||||
{ "alias": "карта распределения ключей", "canonicalId": "seo.keyword_curation_board" },
|
||||
{ "alias": "keyword role", "canonicalId": "seo.keyword_role_decision" },
|
||||
{ "alias": "роль ключа", "canonicalId": "seo.keyword_role_decision" },
|
||||
{ "alias": "keyword map", "canonicalId": "seo.keyword_map" },
|
||||
{ "alias": "landing section mapping", "canonicalId": "seo.landing_section_mapping" },
|
||||
{ "alias": "маппинг посадочных", "canonicalId": "seo.landing_section_mapping" },
|
||||
{ "alias": "future landing gap", "canonicalId": "seo.future_landing_gap" },
|
||||
{ "alias": "будущая посадочная", "canonicalId": "seo.future_landing_gap" },
|
||||
{ "alias": "optimization plan", "canonicalId": "seo.optimization_plan" },
|
||||
{ "alias": "rewrite eligibility", "canonicalId": "seo.rewrite_eligibility" },
|
||||
{ "alias": "semantic block", "canonicalId": "seo.semantic_block" },
|
||||
{ "alias": "семантический блок", "canonicalId": "seo.semantic_block" },
|
||||
{ "alias": "rewrite variant", "canonicalId": "seo.rewrite_variant" },
|
||||
{ "alias": "validation result", "canonicalId": "seo.validation_result" },
|
||||
{ "alias": "change set", "canonicalId": "seo.change_set" },
|
||||
{ "alias": "changeset", "canonicalId": "seo.change_set" },
|
||||
{ "alias": "apply result", "canonicalId": "seo.apply_result" },
|
||||
{ "alias": "coverage expansion", "canonicalId": "seo.expansion_opportunity" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"updatedAt": "2026-07-02",
|
||||
"capabilities": [
|
||||
{ "id": "assistant.seo_read", "entityId": "assistant.capability", "level": "member", "summary": "Assistant can read SEO project context visible to the actor." },
|
||||
{ "id": "assistant.seo_run_analysis", "entityId": "assistant.capability", "level": "member", "summary": "Assistant can request non-destructive SEO scan, ontology extraction, semantic analysis, and evidence collection in an authorized project." },
|
||||
{ "id": "assistant.seo_plan_changes", "entityId": "assistant.capability", "level": "member", "summary": "Assistant can request anchor review, keyword curation, keyword map, optimization plan, rewrite variants, and validation previews." },
|
||||
{ "id": "assistant.seo_apply_changes", "entityId": "assistant.capability", "level": "member", "summary": "Assistant can request an SEO changeset apply only through the SEO-owned adapter, confirmation, validation, and Gateway/app scopes." }
|
||||
],
|
||||
"roleMatrix": [
|
||||
{
|
||||
"roleId": "assistant_member",
|
||||
"allowedCapabilities": [
|
||||
"assistant.seo_read",
|
||||
"assistant.seo_run_analysis",
|
||||
"assistant.seo_plan_changes",
|
||||
"assistant.seo_apply_changes"
|
||||
],
|
||||
"summary": "SEO actions are app-owned and remain bounded by SEO project permissions and Gateway scopes."
|
||||
},
|
||||
{
|
||||
"roleId": "assistant_admin",
|
||||
"allowedCapabilities": [
|
||||
"assistant.seo_read",
|
||||
"assistant.seo_run_analysis",
|
||||
"assistant.seo_plan_changes",
|
||||
"assistant.seo_apply_changes"
|
||||
],
|
||||
"summary": "Assistant admin does not bypass SEO project permissions; it only has the same SEO capabilities plus existing admin capabilities."
|
||||
}
|
||||
],
|
||||
"operations": [
|
||||
{
|
||||
"id": "assistant.op.seo_read_project",
|
||||
"capabilityId": "assistant.seo_read",
|
||||
"sourceEndpoints": ["GET /seo/projects/:projectId"],
|
||||
"requiredGuards": ["requireSeoProjectRead"],
|
||||
"summary": "Read SEO project context through SEO-owned API."
|
||||
},
|
||||
{
|
||||
"id": "assistant.op.seo_run_analysis",
|
||||
"capabilityId": "assistant.seo_run_analysis",
|
||||
"sourceEndpoints": ["POST /seo/projects/:projectId/scan", "POST /seo/projects/:projectId/analysis/semantic", "POST /seo/projects/:projectId/market/collect"],
|
||||
"requiredGuards": ["requireSeoProjectWrite"],
|
||||
"summary": "Run SEO scan and analysis through SEO-owned API."
|
||||
},
|
||||
{
|
||||
"id": "assistant.op.seo_plan_changes",
|
||||
"capabilityId": "assistant.seo_plan_changes",
|
||||
"sourceEndpoints": ["POST /seo/projects/:projectId/anchor-review", "POST /seo/projects/:projectId/keyword-map", "POST /seo/projects/:projectId/optimization-plan", "POST /seo/projects/:projectId/rewrite-variants"],
|
||||
"requiredGuards": ["requireSeoProjectWrite"],
|
||||
"summary": "Prepare anchor review, curated keyword map, SEO plan and rewrite variants without mutating target source."
|
||||
},
|
||||
{
|
||||
"id": "assistant.op.seo_apply_changes",
|
||||
"capabilityId": "assistant.seo_apply_changes",
|
||||
"sourceEndpoints": ["POST /seo/projects/:projectId/changesets/:changesetId/apply"],
|
||||
"requiredGuards": ["requireSeoProjectApply", "requireSeoFinalValidation", "requireConfirmationToken", "requireIdempotencyKey"],
|
||||
"summary": "Apply approved SEO changeset through SEO-owned adapter and safety gates."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"updatedAt": "2026-07-02",
|
||||
"apps": [
|
||||
{
|
||||
"id": "seo",
|
||||
"name": "NDC SEO mod",
|
||||
"authorityEntityIds": ["seo.project", "seo.site", "seo.scope_contract", "seo.change_set"],
|
||||
"adapterId": "adapter.seo.module_api",
|
||||
"summary": "Owns SEO project source/crawl, project ontology extraction, semantic analysis, market evidence, planning, validation, changesets, and apply/export."
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "seo.project.read_context",
|
||||
"app": "seo",
|
||||
"domain": "projects",
|
||||
"intentAliases": ["show seo project", "show site seo context", "покажи seo проект", "покажи сео проект", "покажи контекст сайта"],
|
||||
"entityIds": ["seo.project", "seo.site", "seo.scope_contract", "seo.project_ontology"],
|
||||
"capabilityIds": ["assistant.seo_read"],
|
||||
"riskLevel": "read",
|
||||
"confirmationMode": "none",
|
||||
"selfActionPolicy": "allowed",
|
||||
"adapterId": "adapter.seo.module_api",
|
||||
"adapterStatus": "future",
|
||||
"summary": "Read SEO project context visible to the actor."
|
||||
},
|
||||
{
|
||||
"id": "seo.project.scan",
|
||||
"app": "seo",
|
||||
"domain": "scan",
|
||||
"intentAliases": ["scan seo project", "scan site", "run seo scan", "просканируй сайт", "запусти seo scan", "запусти скан сайта"],
|
||||
"entityIds": ["seo.project", "seo.source", "seo.scan_version"],
|
||||
"capabilityIds": ["assistant.seo_run_analysis"],
|
||||
"riskLevel": "write",
|
||||
"confirmationMode": "explicit",
|
||||
"selfActionPolicy": "allowed",
|
||||
"requiredScopes": ["seo.project.can_run_scan"],
|
||||
"adapterId": "adapter.seo.module_api",
|
||||
"adapterStatus": "future",
|
||||
"summary": "Run SEO project scan and create a new scan_version artifact."
|
||||
},
|
||||
{
|
||||
"id": "seo.ontology.extract",
|
||||
"app": "seo",
|
||||
"domain": "ontology",
|
||||
"intentAliases": ["extract project ontology", "build site ontology", "определи онтологию сайта", "собери онтологию проекта"],
|
||||
"entityIds": ["seo.project", "seo.project_ontology", "seo.semantic_profile"],
|
||||
"capabilityIds": ["assistant.seo_run_analysis"],
|
||||
"riskLevel": "write",
|
||||
"confirmationMode": "explicit",
|
||||
"selfActionPolicy": "allowed",
|
||||
"requiredScopes": ["seo.project.can_run_analysis"],
|
||||
"adapterId": "adapter.seo.module_api",
|
||||
"adapterStatus": "future",
|
||||
"summary": "Extract per-project site ontology from approved source/scope evidence."
|
||||
},
|
||||
{
|
||||
"id": "seo.semantic.analyze",
|
||||
"app": "seo",
|
||||
"domain": "semantic",
|
||||
"intentAliases": ["run semantic analysis", "analyze seo semantics", "проанализируй семантику", "запусти semantic analysis"],
|
||||
"entityIds": ["seo.semantic_profile", "seo.intent_cluster", "seo.demand_universe", "seo.semantic_anchor"],
|
||||
"capabilityIds": ["assistant.seo_run_analysis"],
|
||||
"riskLevel": "write",
|
||||
"confirmationMode": "explicit",
|
||||
"selfActionPolicy": "allowed",
|
||||
"requiredScopes": ["seo.project.can_run_analysis"],
|
||||
"adapterId": "adapter.seo.module_api",
|
||||
"adapterStatus": "future",
|
||||
"summary": "Run semantic analysis bounded by ProjectOntologyInstance and scope contract."
|
||||
},
|
||||
{
|
||||
"id": "seo.anchor_review.approve",
|
||||
"app": "seo",
|
||||
"domain": "market",
|
||||
"intentAliases": ["approve seo anchors", "approve wordstat anchors", "зафиксируй ключи", "подтверди якоря", "зафиксируй смысловые якоря"],
|
||||
"entityIds": ["seo.semantic_anchor", "seo.anchor_review", "seo.wordstat_queue"],
|
||||
"capabilityIds": ["assistant.seo_plan_changes"],
|
||||
"riskLevel": "write",
|
||||
"confirmationMode": "explicit",
|
||||
"selfActionPolicy": "allowed",
|
||||
"requiredScopes": ["seo.project.can_collect_market_evidence"],
|
||||
"adapterId": "adapter.seo.module_api",
|
||||
"adapterStatus": "future",
|
||||
"summary": "Create or update the human-approved anchor review and Wordstat queue before provider collection."
|
||||
},
|
||||
{
|
||||
"id": "seo.market.collect_wordstat",
|
||||
"app": "seo",
|
||||
"domain": "market",
|
||||
"intentAliases": ["collect wordstat", "run wordstat", "собери wordstat", "собери частотности", "запусти вордстат"],
|
||||
"entityIds": ["seo.anchor_review", "seo.wordstat_queue", "seo.seed_query", "seo.market_signal", "seo.keyword"],
|
||||
"capabilityIds": ["assistant.seo_run_analysis"],
|
||||
"riskLevel": "write",
|
||||
"confirmationMode": "explicit",
|
||||
"selfActionPolicy": "allowed",
|
||||
"requiredScopes": ["seo.project.can_collect_market_evidence"],
|
||||
"adapterId": "adapter.seo.module_api",
|
||||
"adapterStatus": "future",
|
||||
"summary": "Collect Wordstat market evidence only for the human-approved Wordstat queue."
|
||||
},
|
||||
{
|
||||
"id": "seo.keyword.curate",
|
||||
"app": "seo",
|
||||
"domain": "keywords",
|
||||
"intentAliases": ["curate keywords", "sort keyword board", "распредели вес ключей", "разложи ключи", "отсортируй ключи"],
|
||||
"entityIds": ["seo.keyword", "seo.keyword_curation_board", "seo.keyword_role_decision"],
|
||||
"capabilityIds": ["assistant.seo_plan_changes"],
|
||||
"riskLevel": "write",
|
||||
"confirmationMode": "explicit",
|
||||
"selfActionPolicy": "allowed",
|
||||
"requiredScopes": ["seo.project.can_plan_changes"],
|
||||
"adapterId": "adapter.seo.module_api",
|
||||
"adapterStatus": "future",
|
||||
"summary": "Pre-sort external keyword candidates into unresolved, landing core, supporting core, differentiators, or long tail lanes for human review."
|
||||
},
|
||||
{
|
||||
"id": "seo.keyword.map",
|
||||
"app": "seo",
|
||||
"domain": "keywords",
|
||||
"intentAliases": ["build keyword map", "map keywords", "собери keyword map", "распредели ключи", "составь карту ключей"],
|
||||
"entityIds": ["seo.keyword", "seo.keyword_curation_board", "seo.keyword_role_decision", "seo.keyword_map", "seo.scope_item"],
|
||||
"capabilityIds": ["assistant.seo_plan_changes"],
|
||||
"riskLevel": "write",
|
||||
"confirmationMode": "explicit",
|
||||
"selfActionPolicy": "allowed",
|
||||
"requiredScopes": ["seo.project.can_plan_changes"],
|
||||
"adapterId": "adapter.seo.module_api",
|
||||
"adapterStatus": "future",
|
||||
"summary": "Build keyword map from curated keyword role decisions and approved scope items."
|
||||
},
|
||||
{
|
||||
"id": "seo.mapping.approve",
|
||||
"app": "seo",
|
||||
"domain": "plan",
|
||||
"intentAliases": ["approve landing mapping", "approve section mapping", "утверди посадочные", "утверди маппинг секций"],
|
||||
"entityIds": ["seo.keyword_map", "seo.landing_section_mapping", "seo.future_landing_gap", "seo.scope_item"],
|
||||
"capabilityIds": ["assistant.seo_plan_changes"],
|
||||
"riskLevel": "write",
|
||||
"confirmationMode": "explicit",
|
||||
"selfActionPolicy": "allowed",
|
||||
"requiredScopes": ["seo.project.can_plan_changes"],
|
||||
"adapterId": "adapter.seo.module_api",
|
||||
"adapterStatus": "future",
|
||||
"summary": "Approve keyword landing/section mapping and keep future landing gaps non-blocking until explicit promotion."
|
||||
},
|
||||
{
|
||||
"id": "seo.plan.build",
|
||||
"app": "seo",
|
||||
"domain": "plan",
|
||||
"intentAliases": ["build optimization plan", "prepare seo plan", "собери план оптимизации", "подготовь seo план"],
|
||||
"entityIds": ["seo.keyword_map", "seo.landing_section_mapping", "seo.future_landing_gap", "seo.optimization_plan", "seo.scope_contract"],
|
||||
"capabilityIds": ["assistant.seo_plan_changes"],
|
||||
"riskLevel": "write",
|
||||
"confirmationMode": "explicit",
|
||||
"selfActionPolicy": "allowed",
|
||||
"requiredScopes": ["seo.project.can_plan_changes"],
|
||||
"adapterId": "adapter.seo.module_api",
|
||||
"adapterStatus": "future",
|
||||
"summary": "Build SEO optimization plan before rewrite."
|
||||
},
|
||||
{
|
||||
"id": "seo.rewrite.propose",
|
||||
"app": "seo",
|
||||
"domain": "rewrite",
|
||||
"intentAliases": ["propose rewrite variants", "generate seo variants", "предложи варианты текста", "сгенерируй rewrite variants"],
|
||||
"entityIds": ["seo.optimization_plan", "seo.rewrite_eligibility", "seo.semantic_block", "seo.rewrite_variant"],
|
||||
"capabilityIds": ["assistant.seo_plan_changes"],
|
||||
"riskLevel": "write",
|
||||
"confirmationMode": "explicit",
|
||||
"selfActionPolicy": "allowed",
|
||||
"requiredScopes": ["seo.project.can_plan_changes"],
|
||||
"adapterId": "adapter.seo.module_api",
|
||||
"adapterStatus": "future",
|
||||
"summary": "Generate rewrite variants into working draft only."
|
||||
},
|
||||
{
|
||||
"id": "seo.validation.run",
|
||||
"app": "seo",
|
||||
"domain": "validation",
|
||||
"intentAliases": ["run seo validation", "validate seo changes", "проверь seo изменения", "запусти финальную проверку"],
|
||||
"entityIds": ["seo.validation_result", "seo.change_set", "seo.rewrite_variant"],
|
||||
"capabilityIds": ["assistant.seo_plan_changes"],
|
||||
"riskLevel": "write",
|
||||
"confirmationMode": "explicit",
|
||||
"selfActionPolicy": "allowed",
|
||||
"requiredScopes": ["seo.project.can_validate_changes"],
|
||||
"adapterId": "adapter.seo.module_api",
|
||||
"adapterStatus": "future",
|
||||
"summary": "Run final SEO validation before changeset apply/export."
|
||||
},
|
||||
{
|
||||
"id": "seo.change_set.preview",
|
||||
"app": "seo",
|
||||
"domain": "changesets",
|
||||
"intentAliases": ["preview seo changeset", "show seo diff", "покажи seo diff", "покажи changeset"],
|
||||
"entityIds": ["seo.change_set", "seo.validation_result"],
|
||||
"capabilityIds": ["assistant.seo_read"],
|
||||
"riskLevel": "read",
|
||||
"confirmationMode": "none",
|
||||
"selfActionPolicy": "allowed",
|
||||
"adapterId": "adapter.seo.module_api",
|
||||
"adapterStatus": "future",
|
||||
"summary": "Preview SEO changeset and validation state without applying changes."
|
||||
},
|
||||
{
|
||||
"id": "seo.change_set.apply",
|
||||
"app": "seo",
|
||||
"domain": "changesets",
|
||||
"intentAliases": ["apply seo changes", "apply seo changeset", "примени seo изменения", "примени changeset"],
|
||||
"entityIds": ["seo.change_set", "seo.validation_result", "seo.apply_result"],
|
||||
"capabilityIds": ["assistant.seo_apply_changes"],
|
||||
"riskLevel": "write",
|
||||
"confirmationMode": "explicit",
|
||||
"selfActionPolicy": "allowed",
|
||||
"requiredScopes": ["seo.project.can_apply_change_set"],
|
||||
"adapterId": "adapter.seo.module_api",
|
||||
"adapterStatus": "future",
|
||||
"summary": "Apply an approved SEO changeset through SEO-owned adapter after validation, backup, idempotency, and explicit confirmation."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"updatedAt": "2026-06-28",
|
||||
"contexts": [],
|
||||
"bindingTypes": [
|
||||
{
|
||||
"id": "binding_type.seo_project_to_ops_context",
|
||||
"relationId": "ontology.resolves_seo_project_to_ops_context",
|
||||
"fromEntityIds": ["seo.project"],
|
||||
"toEntityIds": ["ops.project", "ops.card"],
|
||||
"requiredFromRefs": ["seo_project_id"],
|
||||
"requiredToRefs": ["workspace_slug", "project_id"],
|
||||
"summary": "Binds an SEO project to its OPS Product project/card context."
|
||||
},
|
||||
{
|
||||
"id": "binding_type.ops_context_to_seo_project",
|
||||
"relationId": "ontology.resolves_ops_context_to_seo_project",
|
||||
"fromEntityIds": ["ops.project", "ops.card"],
|
||||
"toEntityIds": ["seo.project"],
|
||||
"requiredFromRefs": ["workspace_slug", "project_id"],
|
||||
"requiredToRefs": ["seo_project_id"],
|
||||
"summary": "Binds OPS Product context to an SEO project."
|
||||
},
|
||||
{
|
||||
"id": "binding_type.hub_app_to_seo_project",
|
||||
"relationId": "ontology.resolves_hub_app_to_seo_project",
|
||||
"fromEntityIds": ["hub.application"],
|
||||
"toEntityIds": ["seo.project"],
|
||||
"requiredFromRefs": ["application_id"],
|
||||
"requiredToRefs": ["seo_project_id"],
|
||||
"summary": "Binds a HUB/Launcher app entry to SEO module project context."
|
||||
}
|
||||
],
|
||||
"bindings": []
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"updatedAt": "2026-07-02",
|
||||
"entities": [
|
||||
{ "id": "seo.project", "name": "SEO Project", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "SEO module project boundary for one connected site/source and its analysis history." },
|
||||
{ "id": "seo.site", "name": "SEO Site", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Analyzed website as seen through source import and/or live crawl." },
|
||||
{ "id": "seo.source", "name": "SEO Source", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Input source: local folder, uploaded package, browser-folder import, repository, or live URL crawl." },
|
||||
{ "id": "seo.scan_version", "name": "SEO Scan Version", "surface": "seo", "status": ["source-evidenced"], "authority": "NDC SEO mod", "summary": "Immutable scan boundary for project index, pages, media, and derived scope." },
|
||||
{ "id": "seo.scope_contract", "name": "SEO Scope Contract", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "User-approved working scope consumed consistently by analysis, planning, rewrite, validation, and apply." },
|
||||
{ "id": "seo.scope_item", "name": "SEO Scope Item", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "One selectable work zone: URL page, generated section, template block, admin page, technical page, content source, or hidden section." },
|
||||
{ "id": "seo.page", "name": "SEO Page", "surface": "seo", "status": ["source-evidenced"], "authority": "NDC SEO mod", "summary": "Logical page or route-level SEO target." },
|
||||
{ "id": "seo.section", "name": "SEO Section", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Stable section/block identity under a page or template, not a fragile DOM selector." },
|
||||
{ "id": "seo.media_asset", "name": "SEO Media Asset", "surface": "seo", "status": ["source-evidenced"], "authority": "NDC SEO mod", "summary": "Image, video, poster, picture/srcset source, CSS background, or other media target with SEO metadata." },
|
||||
{ "id": "seo.project_ontology", "name": "SEO Project Ontology Instance", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod + Ontology Core", "summary": "Versioned per-project site ontology extracted from evidence and aligned with Ontology Core." },
|
||||
{ "id": "seo.semantic_profile", "name": "SEO Semantic Profile", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Structured meaning, tone, entities, intents, gaps, and constraints extracted from selected scope." },
|
||||
{ "id": "seo.intent_cluster", "name": "SEO Intent Cluster", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Search/user intent cluster grounded in project ontology and site evidence." },
|
||||
{ "id": "seo.demand_universe", "name": "SEO Demand Universe", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod + model provider", "summary": "Broad pre-Wordstat meaning and query hypothesis set derived from project context before external evidence collection." },
|
||||
{ "id": "seo.semantic_anchor", "name": "SEO Semantic Anchor", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod + model provider", "summary": "Human-reviewable meaning anchor extracted from project context and used as the only allowed route into Wordstat/SERP queues." },
|
||||
{ "id": "seo.anchor_review", "name": "SEO Anchor Review", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Human approval gate that decides which semantic anchors may be sent to external market evidence providers." },
|
||||
{ "id": "seo.wordstat_queue", "name": "SEO Wordstat Queue", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Explicit queue of human-approved anchor phrases sent to Wordstat evidence collection." },
|
||||
{ "id": "seo.seed_query", "name": "SEO Seed Query", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Candidate phrase sent to market evidence providers after user/model review." },
|
||||
{ "id": "seo.keyword", "name": "SEO Keyword", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Cleaned and classified search phrase with project-level decision state." },
|
||||
{ "id": "seo.market_signal", "name": "SEO Market Signal", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod + external providers", "summary": "Wordstat, SERP, Webmaster, Metrica, or model evidence attached to project-level decisions." },
|
||||
{ "id": "seo.keyword_curation_board", "name": "SEO Keyword Curation Board", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Human-in-the-loop board where external keyword candidates are sorted into SEO roles before strategy." },
|
||||
{ "id": "seo.keyword_role_decision", "name": "SEO Keyword Role Decision", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "User/model-assisted role assignment for a keyword: unresolved, landing core, supporting core, differentiator, or long tail." },
|
||||
{ "id": "seo.keyword_map", "name": "SEO Keyword Map", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Approved mapping from keywords to pages/sections with roles and limits." },
|
||||
{ "id": "seo.landing_section_mapping", "name": "SEO Landing Section Mapping", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Human-approved mapping from curated keyword groups to existing pages, sections, or future landing gaps." },
|
||||
{ "id": "seo.future_landing_gap", "name": "SEO Future Landing Gap", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Non-blocking strategy gap for a potential future landing that must not be materialized or rewritten without explicit promotion." },
|
||||
{ "id": "seo.optimization_plan", "name": "SEO Optimization Plan", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Approved plan of text/meta/media/schema tasks before rewrite." },
|
||||
{ "id": "seo.rewrite_eligibility", "name": "SEO Rewrite Eligibility", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Gate proving that a rewrite target is an approved existing page/section/content-field binding rather than a future gap." },
|
||||
{ "id": "seo.semantic_block", "name": "SEO Semantic Block", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Persisted semantic grouping of page content targets used for block-level rewrite proposals." },
|
||||
{ "id": "seo.rewrite_variant", "name": "SEO Rewrite Variant", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod + model provider", "summary": "Generated optimization variant over working text, not a direct source mutation." },
|
||||
{ "id": "seo.validation_result", "name": "SEO Validation Result", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Final validation output: deterministic checks, over-SEO, language quality, media checks, and hard/soft issues." },
|
||||
{ "id": "seo.change_set", "name": "SEO Change Set", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Explicit diff/apply unit with dry-run, backup, rollback, and export boundaries." },
|
||||
{ "id": "seo.apply_result", "name": "SEO Apply Result", "surface": "seo", "status": ["product-required"], "authority": "NDC SEO mod", "summary": "Result of app-owned apply/export action after confirmation and validation." },
|
||||
{ "id": "seo.expansion_opportunity", "name": "SEO Expansion Opportunity", "surface": "seo", "status": ["future-concept"], "authority": "NDC SEO mod", "summary": "Optional future coverage/growth suggestion after core analysis and market evidence, not part of the current ontology slice." }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"updatedAt": "2026-07-02",
|
||||
"sourceRoots": [
|
||||
{
|
||||
"surface": "seo",
|
||||
"path": "/Users/dcconstructions/Downloads/mnt/NODEDC/NODEDC_SEO/seo_mode/seo_mode",
|
||||
"mode": "read-only architecture/source inspection"
|
||||
}
|
||||
],
|
||||
"ledgers": [
|
||||
{
|
||||
"id": "ledger.seo_domain_v0",
|
||||
"path": "docs/SEO_DOMAIN_ONTOLOGY.md",
|
||||
"entityIds": [
|
||||
"seo.project",
|
||||
"seo.scope_contract",
|
||||
"seo.project_ontology",
|
||||
"seo.semantic_profile",
|
||||
"seo.semantic_anchor",
|
||||
"seo.anchor_review",
|
||||
"seo.wordstat_queue",
|
||||
"seo.market_signal",
|
||||
"seo.keyword_curation_board",
|
||||
"seo.landing_section_mapping",
|
||||
"seo.future_landing_gap",
|
||||
"seo.change_set"
|
||||
]
|
||||
}
|
||||
],
|
||||
"baselineDocs": [
|
||||
"docs/SEO_DOMAIN_ONTOLOGY.md"
|
||||
],
|
||||
"restrictions": [
|
||||
"Do not promote per-project SEO keywords or market rows into global ontology truth.",
|
||||
"Do not let Ontology Core execute SEO source mutations.",
|
||||
"Do not implement optional business expansion before core SEO pipeline evidence is proven."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"updatedAt": "2026-07-02",
|
||||
"rules": [
|
||||
{
|
||||
"id": "guardrail.seo.project_ontology_not_global_truth",
|
||||
"severity": "error",
|
||||
"summary": "Per-project SEO ontology is versioned project evidence; it must not be silently promoted into global Ontology Core truth.",
|
||||
"entityIds": ["seo.project_ontology", "future.domain_package"]
|
||||
},
|
||||
{
|
||||
"id": "guardrail.seo.keywords_are_project_evidence",
|
||||
"severity": "error",
|
||||
"summary": "Keywords, Wordstat rows, SERP rows, and forecasts are project evidence, not reusable ontology roots.",
|
||||
"entityIds": ["seo.keyword", "seo.market_signal", "seo.project_ontology"]
|
||||
},
|
||||
{
|
||||
"id": "guardrail.seo.scope_contract_required",
|
||||
"severity": "error",
|
||||
"summary": "Semantic analysis, keyword map, rewrite, validation, and changeset must be bounded by an approved scope contract.",
|
||||
"entityIds": ["seo.scope_contract", "seo.scope_item", "seo.semantic_profile", "seo.change_set"]
|
||||
},
|
||||
{
|
||||
"id": "guardrail.seo.wordstat_requires_anchor_review",
|
||||
"severity": "error",
|
||||
"summary": "Wordstat/SERP collection must use only human-approved semantic anchors from anchor review, never raw site text or model auto proposals.",
|
||||
"entityIds": ["seo.semantic_anchor", "seo.anchor_review", "seo.wordstat_queue", "seo.market_signal"]
|
||||
},
|
||||
{
|
||||
"id": "guardrail.seo.strategy_requires_curated_keywords",
|
||||
"severity": "error",
|
||||
"summary": "Strategy and optimization plan must consume curated keyword role decisions, not raw provider rows or unreviewed normalization output.",
|
||||
"entityIds": ["seo.keyword_curation_board", "seo.keyword_role_decision", "seo.keyword_map", "seo.optimization_plan"]
|
||||
},
|
||||
{
|
||||
"id": "guardrail.seo.future_landing_gap_non_blocking",
|
||||
"severity": "error",
|
||||
"summary": "Future landing gaps are strategy context only; they must not force materialization, rewrite, or random existing page selection without explicit promotion.",
|
||||
"entityIds": ["seo.future_landing_gap", "seo.landing_section_mapping", "seo.rewrite_eligibility", "seo.rewrite_variant"]
|
||||
},
|
||||
{
|
||||
"id": "guardrail.seo.rewrite_requires_existing_binding",
|
||||
"severity": "error",
|
||||
"summary": "Rewrite variants require approved existing page/section/content-field bindings and must not be generated for unresolved future gaps.",
|
||||
"entityIds": ["seo.rewrite_eligibility", "seo.semantic_block", "seo.section", "seo.rewrite_variant"]
|
||||
},
|
||||
{
|
||||
"id": "guardrail.seo.apply_requires_validation",
|
||||
"severity": "error",
|
||||
"summary": "SEO apply/export requires explicit changeset, final validation, dry-run/backup boundary, and app-owned adapter execution.",
|
||||
"entityIds": ["seo.change_set", "seo.validation_result", "seo.apply_result", "assistant.action"]
|
||||
},
|
||||
{
|
||||
"id": "guardrail.seo.ontology_core_does_not_apply",
|
||||
"severity": "error",
|
||||
"summary": "Ontology Core may resolve SEO action context but must not mutate site sources or execute SEO apply.",
|
||||
"entityIds": ["seo.change_set", "assistant.action", "agent.scope", "agent.audit_event"]
|
||||
},
|
||||
{
|
||||
"id": "guardrail.seo.expansion_is_future_optional_branch",
|
||||
"severity": "warning",
|
||||
"summary": "Expansion opportunities are a future optional branch after core analysis and market evidence; they must not replace current site context by default.",
|
||||
"entityIds": ["seo.expansion_opportunity", "seo.market_signal", "seo.optimization_plan"]
|
||||
}
|
||||
],
|
||||
"blockedConflations": [
|
||||
["seo.project_ontology", "future.domain_package"],
|
||||
["seo.keyword", "future.domain_package"],
|
||||
["seo.semantic_anchor", "seo.market_signal"],
|
||||
["seo.anchor_review", "seo.market_signal"],
|
||||
["seo.market_signal", "seo.intent_cluster"],
|
||||
["seo.keyword_curation_board", "seo.keyword_map"],
|
||||
["seo.future_landing_gap", "seo.page"],
|
||||
["seo.expansion_opportunity", "seo.optimization_plan"],
|
||||
["seo.change_set", "assistant.action"]
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"id": "seo",
|
||||
"version": "0.1.0",
|
||||
"updatedAt": "2026-06-28",
|
||||
"status": "product-required/pending-source-pass",
|
||||
"summary": "SEO module domain ontology package for site scans, project ontology, scope contracts, semantic analysis, market evidence, optimization plans, validation, and changesets."
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://nodedc.local/ontology/seo/project-ontology.schema.json",
|
||||
"title": "SEO Project Ontology Instance",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"schemaVersion",
|
||||
"projectId",
|
||||
"siteId",
|
||||
"coreOntologyVersion",
|
||||
"seoDomainPackageVersion",
|
||||
"source",
|
||||
"scope",
|
||||
"brand",
|
||||
"pages",
|
||||
"sections",
|
||||
"intentClusters",
|
||||
"tone",
|
||||
"constraints",
|
||||
"evidence",
|
||||
"approvals"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"schemaVersion": { "const": "seo-project-ontology.v1" },
|
||||
"projectId": { "type": "string", "minLength": 1 },
|
||||
"siteId": { "type": "string", "minLength": 1 },
|
||||
"coreOntologyVersion": { "type": "string", "minLength": 1 },
|
||||
"seoDomainPackageVersion": { "type": "string", "minLength": 1 },
|
||||
"source": {
|
||||
"type": "object",
|
||||
"required": ["sourceType", "scanVersionId"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"sourceType": {
|
||||
"type": "string",
|
||||
"enum": ["local_folder", "browser_folder", "uploaded_package", "repository", "live_crawl"]
|
||||
},
|
||||
"scanVersionId": { "type": "string", "minLength": 1 },
|
||||
"rootUrl": { "type": ["string", "null"] },
|
||||
"sourcePath": { "type": ["string", "null"] }
|
||||
}
|
||||
},
|
||||
"scope": {
|
||||
"type": "object",
|
||||
"required": ["scopeContractId", "scopeItemIds"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"scopeContractId": { "type": "string", "minLength": 1 },
|
||||
"scopeItemIds": {
|
||||
"type": "array",
|
||||
"items": { "type": "string", "minLength": 1 },
|
||||
"uniqueItems": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"brand": {
|
||||
"type": "object",
|
||||
"required": ["name", "aliases", "description"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": { "type": "string", "minLength": 1 },
|
||||
"aliases": { "type": "array", "items": { "type": "string" } },
|
||||
"description": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"offers": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/namedEvidenceItem" }
|
||||
},
|
||||
"audiences": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/namedEvidenceItem" }
|
||||
},
|
||||
"pages": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["id", "url", "title", "evidenceIds"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": { "type": "string", "minLength": 1 },
|
||||
"url": { "type": "string" },
|
||||
"title": { "type": "string" },
|
||||
"evidenceIds": { "$ref": "#/$defs/evidenceIds" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["id", "pageId", "title", "meaning", "evidenceIds"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": { "type": "string", "minLength": 1 },
|
||||
"pageId": { "type": "string", "minLength": 1 },
|
||||
"title": { "type": "string" },
|
||||
"meaning": { "type": "string" },
|
||||
"evidenceIds": { "$ref": "#/$defs/evidenceIds" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"intentClusters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["id", "title", "priority", "targetIntent", "seedQueries", "evidenceIds"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": { "type": "string", "minLength": 1 },
|
||||
"title": { "type": "string", "minLength": 1 },
|
||||
"priority": { "type": "string", "enum": ["high", "medium", "low"] },
|
||||
"targetIntent": { "type": "string" },
|
||||
"seedQueries": {
|
||||
"type": "array",
|
||||
"items": { "type": "string", "minLength": 1 }
|
||||
},
|
||||
"evidenceIds": { "$ref": "#/$defs/evidenceIds" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"tone": {
|
||||
"type": "object",
|
||||
"required": ["language", "signals", "forbiddenShifts"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"language": { "type": "string" },
|
||||
"signals": { "type": "array", "items": { "type": "string" } },
|
||||
"forbiddenShifts": { "type": "array", "items": { "type": "string" } }
|
||||
}
|
||||
},
|
||||
"constraints": {
|
||||
"type": "object",
|
||||
"required": ["forbiddenTopics", "forbiddenClaims", "mustKeep"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"forbiddenTopics": { "type": "array", "items": { "type": "string" } },
|
||||
"forbiddenClaims": { "type": "array", "items": { "type": "string" } },
|
||||
"mustKeep": { "type": "array", "items": { "type": "string" } }
|
||||
}
|
||||
},
|
||||
"evidence": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["id", "sourceType", "ref", "summary"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": { "type": "string", "minLength": 1 },
|
||||
"sourceType": { "type": "string" },
|
||||
"ref": { "type": "string" },
|
||||
"summary": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"approvals": {
|
||||
"type": "object",
|
||||
"required": ["status", "approvedBy", "approvedAt"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"status": { "type": "string", "enum": ["draft", "approved", "rejected", "needs_review"] },
|
||||
"approvedBy": { "type": ["string", "null"] },
|
||||
"approvedAt": { "type": ["string", "null"] }
|
||||
}
|
||||
},
|
||||
"ontologyDeltaProposals": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["id", "target", "summary", "status"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": { "type": "string", "minLength": 1 },
|
||||
"target": { "type": "string", "enum": ["core", "seo_domain", "project_only"] },
|
||||
"summary": { "type": "string" },
|
||||
"status": { "type": "string", "enum": ["proposed", "accepted", "rejected", "deferred"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"evidenceIds": {
|
||||
"type": "array",
|
||||
"items": { "type": "string", "minLength": 1 },
|
||||
"uniqueItems": true
|
||||
},
|
||||
"namedEvidenceItem": {
|
||||
"type": "object",
|
||||
"required": ["id", "name", "summary", "evidenceIds"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": { "type": "string", "minLength": 1 },
|
||||
"name": { "type": "string", "minLength": 1 },
|
||||
"summary": { "type": "string" },
|
||||
"evidenceIds": { "$ref": "#/$defs/evidenceIds" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"updatedAt": "2026-07-02",
|
||||
"relations": [
|
||||
{ "id": "seo.project.has_site", "from": ["seo.project"], "to": ["seo.site"], "status": "product-required", "summary": "SEO project analyzes one or more site contexts." },
|
||||
{ "id": "seo.project.uses_source", "from": ["seo.project"], "to": ["seo.source"], "status": "product-required", "summary": "SEO project is connected to import/crawl source." },
|
||||
{ "id": "seo.project.has_project_ontology", "from": ["seo.project"], "to": ["seo.project_ontology"], "status": "product-required", "summary": "Per-project ontology profile belongs to SEO project." },
|
||||
{ "id": "seo.source.produces_scan_version", "from": ["seo.source"], "to": ["seo.scan_version"], "status": "product-required", "summary": "Source import/crawl creates immutable scan version." },
|
||||
{ "id": "seo.scan_version.produces_scope_contract", "from": ["seo.scan_version"], "to": ["seo.scope_contract"], "status": "product-required", "summary": "Scan and user configuration produce approved scope contract." },
|
||||
{ "id": "seo.scope_contract.contains_scope_item", "from": ["seo.scope_contract"], "to": ["seo.scope_item"], "status": "product-required", "summary": "Scope contract is composed of selected work zones." },
|
||||
{ "id": "seo.scope_item.targets_page", "from": ["seo.scope_item"], "to": ["seo.page"], "status": "product-required", "summary": "Scope item can target a logical page." },
|
||||
{ "id": "seo.scope_item.targets_section", "from": ["seo.scope_item"], "to": ["seo.section"], "status": "product-required", "summary": "Scope item can target a stable section/block." },
|
||||
{ "id": "seo.site.has_page", "from": ["seo.site"], "to": ["seo.page"], "status": "product-required", "summary": "Site contains logical pages/routes." },
|
||||
{ "id": "seo.page.has_section", "from": ["seo.page"], "to": ["seo.section"], "status": "product-required", "summary": "Page can contain stable sections/blocks." },
|
||||
{ "id": "seo.section.contains_media", "from": ["seo.section"], "to": ["seo.media_asset"], "status": "product-required", "summary": "Media is attached to a section when context can be resolved." },
|
||||
{ "id": "seo.semantic_profile.uses_project_ontology", "from": ["seo.semantic_profile"], "to": ["seo.project_ontology"], "status": "product-required", "summary": "Semantic analysis is grounded in per-project ontology." },
|
||||
{ "id": "seo.semantic_profile.describes_scope_contract", "from": ["seo.semantic_profile"], "to": ["seo.scope_contract"], "status": "product-required", "summary": "Semantic profile describes selected work scope, not arbitrary site fantasy." },
|
||||
{ "id": "seo.semantic_profile.produces_demand_universe", "from": ["seo.semantic_profile"], "to": ["seo.demand_universe"], "status": "product-required", "summary": "Selected project context produces a broad demand universe before provider calls." },
|
||||
{ "id": "seo.demand_universe.contains_semantic_anchor", "from": ["seo.demand_universe"], "to": ["seo.semantic_anchor"], "status": "product-required", "summary": "Demand universe contains semantic anchors that can be reviewed for market routing." },
|
||||
{ "id": "seo.semantic_anchor.requires_anchor_review", "from": ["seo.semantic_anchor"], "to": ["seo.anchor_review"], "status": "product-required", "summary": "A semantic anchor cannot enter Wordstat/SERP routing before human anchor review." },
|
||||
{ "id": "seo.anchor_review.approves_wordstat_queue", "from": ["seo.anchor_review"], "to": ["seo.wordstat_queue"], "status": "product-required", "summary": "Anchor review creates the explicit Wordstat queue from approved anchors." },
|
||||
{ "id": "seo.intent_cluster.suggests_seed_query", "from": ["seo.intent_cluster"], "to": ["seo.seed_query"], "status": "product-required", "summary": "Intent cluster can suggest seed queries for evidence collection." },
|
||||
{ "id": "seo.wordstat_queue.contains_seed_query", "from": ["seo.wordstat_queue"], "to": ["seo.seed_query"], "status": "product-required", "summary": "Only human-approved Wordstat queue items become executable seed queries." },
|
||||
{ "id": "seo.wordstat_queue.produces_market_signal", "from": ["seo.wordstat_queue"], "to": ["seo.market_signal"], "status": "product-required", "summary": "Wordstat collection stores provider results as market signals tied to the approved queue." },
|
||||
{ "id": "seo.seed_query.expands_to_keyword", "from": ["seo.seed_query"], "to": ["seo.keyword"], "status": "product-required", "summary": "Approved seed query can produce cleaned keyword candidates." },
|
||||
{ "id": "seo.keyword.validated_by_market_signal", "from": ["seo.keyword"], "to": ["seo.market_signal"], "status": "product-required", "summary": "Keyword decisions require market evidence when available." },
|
||||
{ "id": "seo.keyword_curation_board.classifies_keyword", "from": ["seo.keyword_curation_board"], "to": ["seo.keyword_role_decision", "seo.keyword"], "status": "product-required", "summary": "Keyword curation board assigns human-reviewed roles to keyword candidates." },
|
||||
{ "id": "seo.keyword_role_decision.produces_keyword_map", "from": ["seo.keyword_role_decision"], "to": ["seo.keyword_map"], "status": "product-required", "summary": "Only curated keyword role decisions can feed the keyword map used by strategy." },
|
||||
{ "id": "seo.keyword_map.assigns_keyword", "from": ["seo.keyword_map"], "to": ["seo.keyword", "seo.scope_item"], "status": "product-required", "summary": "Keyword map assigns keywords to approved scope items." },
|
||||
{ "id": "seo.keyword_map.requires_landing_section_mapping", "from": ["seo.keyword_map"], "to": ["seo.landing_section_mapping"], "status": "product-required", "summary": "Keyword map must be resolved to approved existing pages/sections or explicit future gaps before rewrite." },
|
||||
{ "id": "seo.landing_section_mapping.maps_keyword_to_scope_item", "from": ["seo.landing_section_mapping"], "to": ["seo.keyword", "seo.scope_item"], "status": "product-required", "summary": "Landing/section mapping binds curated keywords to existing approved scope items." },
|
||||
{ "id": "seo.landing_section_mapping.identifies_future_gap", "from": ["seo.landing_section_mapping"], "to": ["seo.future_landing_gap"], "status": "product-required", "summary": "A mapping can explicitly mark a keyword group as a non-blocking future landing gap." },
|
||||
{ "id": "seo.optimization_plan.uses_keyword_map", "from": ["seo.optimization_plan"], "to": ["seo.keyword_map"], "status": "product-required", "summary": "Optimization plan is based on approved keyword map." },
|
||||
{ "id": "seo.optimization_plan.targets_scope_contract", "from": ["seo.optimization_plan"], "to": ["seo.scope_contract"], "status": "product-required", "summary": "Optimization plan targets approved scope contract." },
|
||||
{ "id": "seo.rewrite_eligibility.requires_landing_section_mapping", "from": ["seo.rewrite_eligibility"], "to": ["seo.landing_section_mapping"], "status": "product-required", "summary": "Rewrite eligibility requires an approved mapping to an existing source-backed target." },
|
||||
{ "id": "seo.semantic_block.groups_section", "from": ["seo.semantic_block"], "to": ["seo.section", "seo.scope_item"], "status": "product-required", "summary": "Semantic blocks group source-backed sections/scope items for block-level rewrite proposals." },
|
||||
{ "id": "seo.rewrite_variant.requires_rewrite_eligibility", "from": ["seo.rewrite_variant"], "to": ["seo.rewrite_eligibility", "seo.semantic_block"], "status": "product-required", "summary": "Rewrite variants can be proposed only for eligible existing bindings, optionally grouped by semantic block." },
|
||||
{ "id": "seo.rewrite_variant.generated_from_plan", "from": ["seo.rewrite_variant"], "to": ["seo.optimization_plan"], "status": "product-required", "summary": "Rewrite variant is generated from approved optimization plan." },
|
||||
{ "id": "seo.validation_result.validates_change_set", "from": ["seo.validation_result"], "to": ["seo.change_set"], "status": "product-required", "summary": "Change set must be validated before apply." },
|
||||
{ "id": "seo.change_set.applies_to_scope_contract", "from": ["seo.change_set"], "to": ["seo.scope_contract"], "status": "product-required", "summary": "Change set is bounded by approved scope contract." },
|
||||
{ "id": "seo.change_set.produces_apply_result", "from": ["seo.change_set"], "to": ["seo.apply_result"], "status": "product-required", "summary": "Apply/export produces a traceable result." },
|
||||
{ "id": "seo.expansion_opportunity.requires_market_signal", "from": ["seo.expansion_opportunity"], "to": ["seo.market_signal"], "status": "future-concept", "summary": "Future expansion proposals must be grounded in market evidence." },
|
||||
{ "id": "ontology.resolves_seo_project_to_ops_context", "from": ["seo.project"], "to": ["ops.project", "ops.card"], "status": "product-required", "summary": "Assistant can route SEO project work to the correct OPS Product context." },
|
||||
{ "id": "ontology.resolves_ops_context_to_seo_project", "from": ["ops.project", "ops.card"], "to": ["seo.project"], "status": "product-required", "summary": "Assistant can route OPS Product context into SEO module project context when bound." },
|
||||
{ "id": "ontology.resolves_hub_app_to_seo_project", "from": ["hub.application"], "to": ["seo.project"], "status": "product-required", "summary": "Launcher/HUB application context can suggest SEO module project context." }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"updatedAt": "2026-06-28",
|
||||
"rules": [
|
||||
{
|
||||
"id": "resolver.seo_to_ops.project_context",
|
||||
"relationId": "ontology.resolves_seo_project_to_ops_context",
|
||||
"fromSurface": "seo",
|
||||
"inputEntityIds": ["seo.project", "seo.site", "seo.scope_contract"],
|
||||
"intentHints": ["ops", "card", "task", "issue", "project", "report", "карточ", "задач", "отчет"],
|
||||
"outputSurface": "ops",
|
||||
"outputEntityIds": ["ops.workspace", "ops.project", "ops.card", "ops.card_type"],
|
||||
"requiredBindings": ["seo.project -> ops.project|ops.card context"],
|
||||
"status": "product-required",
|
||||
"summary": "Route SEO module project context into OPS Product work context."
|
||||
},
|
||||
{
|
||||
"id": "resolver.ops_to_seo.project_context",
|
||||
"relationId": "ontology.resolves_ops_context_to_seo_project",
|
||||
"fromSurface": "ops",
|
||||
"inputEntityIds": ["ops.project", "ops.card"],
|
||||
"intentHints": ["seo", "site", "semantic", "wordstat", "keyword", "rewrite", "validation", "сео", "сайт", "семантик"],
|
||||
"outputSurface": "seo",
|
||||
"outputEntityIds": ["seo.project", "seo.site", "seo.scope_contract", "seo.project_ontology"],
|
||||
"requiredBindings": ["ops.project|ops.card -> seo.project context"],
|
||||
"status": "product-required",
|
||||
"summary": "Route OPS Product context into SEO module project context."
|
||||
},
|
||||
{
|
||||
"id": "resolver.hub_app_to_seo.project_context",
|
||||
"relationId": "ontology.resolves_hub_app_to_seo_project",
|
||||
"fromSurface": "hub",
|
||||
"inputEntityIds": ["hub.application"],
|
||||
"intentHints": ["seo", "site", "project", "audit", "сео", "сайт", "аудит"],
|
||||
"outputSurface": "seo",
|
||||
"outputEntityIds": ["seo.project", "seo.site"],
|
||||
"requiredBindings": ["hub.application -> seo.project context"],
|
||||
"status": "product-required",
|
||||
"summary": "Route Launcher app context into SEO module project context."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
# Project Ontology Instance Schema
|
||||
|
||||
Status: first contract
|
||||
Date: 2026-06-28
|
||||
Owner: NDC SEO mod
|
||||
|
||||
## Intent
|
||||
|
||||
`ProjectOntologyInstance` is the per-project/site ontology profile consumed by NDC SEO mod.
|
||||
It is not the global ontology.
|
||||
|
||||
It gives the model enough grounded context to analyze and rewrite the current site without
|
||||
injecting unrelated offers, markets, or product claims.
|
||||
|
||||
## Required Guarantees
|
||||
|
||||
- Every extracted entity, intent, tone rule, and constraint must have evidence or explicit user approval.
|
||||
- Unknown entities are allowed but must stay local to the project until promoted.
|
||||
- Market evidence is linked as project evidence, not ontology truth.
|
||||
- Rewrite and apply stages must only consume approved scope items.
|
||||
- Optional expansion proposals are not part of the default optimization path.
|
||||
|
||||
## JSON Fields
|
||||
|
||||
Top level:
|
||||
|
||||
- `schemaVersion`
|
||||
- `projectId`
|
||||
- `siteId`
|
||||
- `coreOntologyVersion`
|
||||
- `seoDomainPackageVersion`
|
||||
- `source`
|
||||
- `scope`
|
||||
- `brand`
|
||||
- `offers`
|
||||
- `audiences`
|
||||
- `pages`
|
||||
- `sections`
|
||||
- `intentClusters`
|
||||
- `tone`
|
||||
- `constraints`
|
||||
- `evidence`
|
||||
- `approvals`
|
||||
- `ontologyDeltaProposals`
|
||||
|
||||
## Lifecycle
|
||||
|
||||
1. Source import or live crawl creates `scan_version`.
|
||||
2. User-selected graph/scope creates `scope_contract`.
|
||||
3. Extractor creates a draft `ProjectOntologyInstance`.
|
||||
4. Dev Mode shows evidence, confidence, and unresolved questions.
|
||||
5. User/model approvals produce an approved version.
|
||||
6. Semantic analysis consumes approved ontology plus scope contract.
|
||||
7. Market evidence, keyword map, plan, rewrite, validation, and changeset link back to ontology version.
|
||||
|
||||
## NODE.DC Seed
|
||||
|
||||
The NODE.DC project ontology seed should be kept as a fixture and regression target.
|
||||
It must not stay embedded inside generic semantic analysis code.
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
# SEO Domain Ontology
|
||||
|
||||
Status: Stage 5 architecture slice
|
||||
Date: 2026-07-02
|
||||
Package: `catalog/domain-packages/seo`
|
||||
|
||||
## Purpose
|
||||
|
||||
SEO Domain Ontology defines the reusable platform vocabulary for NDC SEO mod.
|
||||
It must not replace per-project evidence and must not execute SEO mutations.
|
||||
|
||||
The package exists so the SEO module can participate in NodeDC-wide assistant routing
|
||||
without hardcoding NODE.DC-specific product clusters into the generic SEO engine.
|
||||
|
||||
## Ownership Boundaries
|
||||
|
||||
Ontology Core owns:
|
||||
|
||||
- canonical SEO entity/action names;
|
||||
- aliases;
|
||||
- relations;
|
||||
- guardrails;
|
||||
- assistant action cards;
|
||||
- resolver rules and binding types.
|
||||
|
||||
NDC SEO mod owns:
|
||||
|
||||
- project source import and live crawl;
|
||||
- scan versions;
|
||||
- project ontology extraction;
|
||||
- semantic analysis;
|
||||
- Wordstat/SERP/Webmaster/Metrica evidence collection;
|
||||
- keyword decisions;
|
||||
- optimization plans;
|
||||
- rewrite variants;
|
||||
- final validation;
|
||||
- changeset/apply/export execution.
|
||||
|
||||
OPS Gateway and app-owned adapters own:
|
||||
|
||||
- permission enforcement;
|
||||
- scopes/grants;
|
||||
- confirmation;
|
||||
- idempotency;
|
||||
- audit.
|
||||
|
||||
## Three-Layer Model
|
||||
|
||||
`CoreOntologySnapshot`:
|
||||
|
||||
- stable platform meanings from Ontology Core;
|
||||
- HUB, OPS Product, OPS Gateway, ENGINE, Assistant, and Future Interface Layer concepts;
|
||||
- SEO package-level concepts after validation.
|
||||
|
||||
`SeoDomainOntology`:
|
||||
|
||||
- reusable `seo.*` entity/action/guardrail catalog;
|
||||
- not tied to one site;
|
||||
- ships with Ontology Core as a domain package.
|
||||
|
||||
`ProjectOntologyInstance`:
|
||||
|
||||
- versioned project/site evidence;
|
||||
- extracted from source/crawl/content and user approvals;
|
||||
- contains brand, offers, audiences, pages, sections, tone, constraints, entities, intents, and evidence refs;
|
||||
- can propose ontology deltas but cannot silently mutate Ontology Core.
|
||||
|
||||
## Current SEO Entities
|
||||
|
||||
The first package slice defines:
|
||||
|
||||
- `seo.project`
|
||||
- `seo.site`
|
||||
- `seo.source`
|
||||
- `seo.scan_version`
|
||||
- `seo.scope_contract`
|
||||
- `seo.scope_item`
|
||||
- `seo.page`
|
||||
- `seo.section`
|
||||
- `seo.media_asset`
|
||||
- `seo.project_ontology`
|
||||
- `seo.semantic_profile`
|
||||
- `seo.intent_cluster`
|
||||
- `seo.demand_universe`
|
||||
- `seo.semantic_anchor`
|
||||
- `seo.anchor_review`
|
||||
- `seo.wordstat_queue`
|
||||
- `seo.seed_query`
|
||||
- `seo.keyword`
|
||||
- `seo.market_signal`
|
||||
- `seo.keyword_curation_board`
|
||||
- `seo.keyword_role_decision`
|
||||
- `seo.keyword_map`
|
||||
- `seo.landing_section_mapping`
|
||||
- `seo.future_landing_gap`
|
||||
- `seo.optimization_plan`
|
||||
- `seo.rewrite_eligibility`
|
||||
- `seo.semantic_block`
|
||||
- `seo.rewrite_variant`
|
||||
- `seo.validation_result`
|
||||
- `seo.change_set`
|
||||
- `seo.apply_result`
|
||||
- `seo.expansion_opportunity`
|
||||
|
||||
`seo.expansion_opportunity` is deliberately marked `future-concept`.
|
||||
It exists only to reserve the boundary for a later optional business/coverage branch.
|
||||
|
||||
## Assistant Actions
|
||||
|
||||
SEO assistant actions are registered as declarative future app-owned actions:
|
||||
|
||||
- `seo.project.read_context`
|
||||
- `seo.project.scan`
|
||||
- `seo.ontology.extract`
|
||||
- `seo.semantic.analyze`
|
||||
- `seo.anchor_review.approve`
|
||||
- `seo.market.collect_wordstat`
|
||||
- `seo.keyword.curate`
|
||||
- `seo.keyword.map`
|
||||
- `seo.mapping.approve`
|
||||
- `seo.plan.build`
|
||||
- `seo.rewrite.propose`
|
||||
- `seo.validation.run`
|
||||
- `seo.change_set.preview`
|
||||
- `seo.change_set.apply`
|
||||
|
||||
All actions route to `adapter.seo.module_api` and currently use `adapterStatus: future`.
|
||||
This is intentional: Ontology Core can resolve and classify intent before the SEO adapter exists,
|
||||
but it must not pretend that live execution is ready.
|
||||
|
||||
## Guardrails
|
||||
|
||||
Hard rules:
|
||||
|
||||
- per-project SEO ontology is evidence, not global truth;
|
||||
- keywords, Wordstat rows, SERP rows, and forecasts stay project evidence;
|
||||
- semantic analysis, keyword map, rewrite, validation, and changeset must be bounded by scope contract;
|
||||
- Wordstat/SERP collection must use only human-approved anchors from `seo.anchor_review`, never raw site text or automatic model proposals;
|
||||
- strategy and optimization plan must consume curated keyword role decisions, not raw provider rows;
|
||||
- future landing gaps are non-blocking strategy context and must not force materialization, rewrite, or random existing page selection;
|
||||
- rewrite variants require approved existing page/section/content-field bindings and may be grouped by persisted semantic blocks;
|
||||
- apply requires changeset, validation, dry-run/backup boundary, confirmation, and app-owned adapter;
|
||||
- Ontology Core does not execute SEO source mutations.
|
||||
|
||||
## Stage 5 Keyword Curation
|
||||
|
||||
The model must pre-sort keyword candidates before strategy. The board has exactly five lanes:
|
||||
|
||||
- unresolved / `Неразобранные`;
|
||||
- landing core / `Посадочное ядро`;
|
||||
- supporting core / `Поддерживающее ядро`;
|
||||
- differentiators / `Уточняющие / УТП`;
|
||||
- long tail / `Длинный хвост`.
|
||||
|
||||
The user can move cards between lanes. Final strategy input includes only landing core,
|
||||
supporting core, differentiators, and long tail. Unresolved keywords do not participate in
|
||||
strategy, mapping, rewrite, or materialization.
|
||||
|
||||
Optional future rule:
|
||||
|
||||
- expansion opportunities are a separate future branch after core SEO results and market evidence.
|
||||
|
||||
## Current Runtime Gap
|
||||
|
||||
The SEO module has moved NODE.DC-specific clusters behind an explicit project ontology seed fixture.
|
||||
Generic extraction must not inject NODE.DC aliases, `/modules/ops`, or AI-platform fixture phrases.
|
||||
This is covered by:
|
||||
|
||||
```text
|
||||
/Users/dcconstructions/Downloads/mnt/NODEDC/NODEDC_SEO/seo_mode/seo_mode/server/src/scripts/checkOntologyUniversality.ts
|
||||
```
|
||||
|
||||
The remaining gap is execution routing: Ontology Core declares SEO actions and guardrails, but the live
|
||||
`adapter.seo.module_api` is still marked `future`. Assistant/MCP execution must follow the SEO-owned API
|
||||
contract before it can simulate the full route:
|
||||
|
||||
```text
|
||||
semantic profile -> demand universe -> anchor review -> Wordstat queue -> market signal -> keyword curation -> landing/section mapping -> strategy/rewrite eligibility
|
||||
```
|
||||
|
||||
## Package Loading
|
||||
|
||||
Ontology Core now loads domain packages from:
|
||||
|
||||
```text
|
||||
catalog/domain-packages/*
|
||||
```
|
||||
|
||||
Each package may contribute:
|
||||
|
||||
- `entities.json`
|
||||
- `relations.json`
|
||||
- `aliases.json`
|
||||
- `guardrails.json`
|
||||
- `evidence.json`
|
||||
- `resolver-rules.json`
|
||||
- `context-bindings.json`
|
||||
- `assistant-access-policy.json`
|
||||
- `assistant-actions.json`
|
||||
- `assistant-risk-policy.json`
|
||||
|
||||
The loader merges package catalogs into the base catalog before validation.
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"schemaVersion": "seo-project-ontology.v1",
|
||||
"projectId": "nodedc-site-seo",
|
||||
"siteId": "nodedc-site",
|
||||
"coreOntologyVersion": "0.4-pre",
|
||||
"seoDomainPackageVersion": "0.1.0",
|
||||
"source": {
|
||||
"sourceType": "local_folder",
|
||||
"scanVersionId": "scan_fixture_nodedc_site",
|
||||
"rootUrl": null,
|
||||
"sourcePath": "/Users/dcconstructions/Downloads/mnt/NODEDC/NODEDC_SITE"
|
||||
},
|
||||
"scope": {
|
||||
"scopeContractId": "scope_fixture_nodedc_site",
|
||||
"scopeItemIds": ["page.home", "section.hero", "section.modules", "section.ops", "section.engine"]
|
||||
},
|
||||
"brand": {
|
||||
"name": "NODE.DC",
|
||||
"aliases": ["NodeDC", "NDC", "NODEDC"],
|
||||
"description": "Enterprise platform for process automation, AI assistants, workflow, operational control, and applied modules."
|
||||
},
|
||||
"offers": [
|
||||
{
|
||||
"id": "offer.platform",
|
||||
"name": "AI platform for business automation",
|
||||
"summary": "Platform layer combining Hub, Engine, Ops, Assistant, integrations, and domain modules.",
|
||||
"evidenceIds": ["e.site.copy"]
|
||||
},
|
||||
{
|
||||
"id": "offer.ops",
|
||||
"name": "Operational control and project workspace",
|
||||
"summary": "Operational card/project/task context for teams and AI-assisted execution.",
|
||||
"evidenceIds": ["e.ops.card"]
|
||||
},
|
||||
{
|
||||
"id": "offer.engine",
|
||||
"name": "Workflow and automation engine",
|
||||
"summary": "Workflow graph and automation runtime context connected to platform apps.",
|
||||
"evidenceIds": ["e.ontology.core"]
|
||||
}
|
||||
],
|
||||
"audiences": [
|
||||
{
|
||||
"id": "audience.enterprise",
|
||||
"name": "Enterprise teams",
|
||||
"summary": "Teams needing controlled automation, access boundaries, workflow, and operational visibility.",
|
||||
"evidenceIds": ["e.site.copy"]
|
||||
}
|
||||
],
|
||||
"pages": [
|
||||
{
|
||||
"id": "page.home",
|
||||
"url": "/",
|
||||
"title": "NODE.DC landing",
|
||||
"evidenceIds": ["e.site.copy"]
|
||||
}
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "section.hero",
|
||||
"pageId": "page.home",
|
||||
"title": "Hero",
|
||||
"meaning": "Core platform positioning and primary offer.",
|
||||
"evidenceIds": ["e.site.copy"]
|
||||
},
|
||||
{
|
||||
"id": "section.modules",
|
||||
"pageId": "page.home",
|
||||
"title": "Modules",
|
||||
"meaning": "Platform modules and application catalog.",
|
||||
"evidenceIds": ["e.site.copy"]
|
||||
},
|
||||
{
|
||||
"id": "section.ops",
|
||||
"pageId": "page.home",
|
||||
"title": "Ops",
|
||||
"meaning": "Operational work management, projects, cards, statuses, and assistant routing.",
|
||||
"evidenceIds": ["e.ops.card"]
|
||||
},
|
||||
{
|
||||
"id": "section.engine",
|
||||
"pageId": "page.home",
|
||||
"title": "Engine",
|
||||
"meaning": "Workflow graph, automation nodes, runtime context, and integrations.",
|
||||
"evidenceIds": ["e.ontology.core"]
|
||||
}
|
||||
],
|
||||
"intentClusters": [
|
||||
{
|
||||
"id": "intent.ai_platform_development",
|
||||
"title": "AI platform for development and automation",
|
||||
"priority": "high",
|
||||
"targetIntent": "Commercial platform, automation, AI, product development, enterprise workflow.",
|
||||
"seedQueries": ["ai платформа для бизнеса", "платформа автоматизации бизнеса ai"],
|
||||
"evidenceIds": ["e.site.copy"]
|
||||
},
|
||||
{
|
||||
"id": "intent.business_process_automation",
|
||||
"title": "Business process automation",
|
||||
"priority": "high",
|
||||
"targetIntent": "BPM/workflow/process automation and operational execution.",
|
||||
"seedQueries": ["автоматизация бизнес процессов", "workflow система для бизнеса"],
|
||||
"evidenceIds": ["e.site.copy"]
|
||||
},
|
||||
{
|
||||
"id": "intent.agentic_automation",
|
||||
"title": "Agentic automation and AI assistants",
|
||||
"priority": "high",
|
||||
"targetIntent": "AI agents, assistants, autonomous checks, and task execution.",
|
||||
"seedQueries": ["ai агенты для бизнеса", "агентная автоматизация процессов"],
|
||||
"evidenceIds": ["e.site.copy"]
|
||||
}
|
||||
],
|
||||
"tone": {
|
||||
"language": "ru",
|
||||
"signals": ["engineering product tone", "B2B operational language", "AI-first vocabulary"],
|
||||
"forbiddenShifts": ["consumer entertainment tone", "unsupported market claims", "global business pivot without approval"]
|
||||
},
|
||||
"constraints": {
|
||||
"forbiddenTopics": ["old Webflow context", "SS-script legacy context", "federat legacy context"],
|
||||
"forbiddenClaims": ["guaranteed traffic growth without market evidence", "unsupported integrations"],
|
||||
"mustKeep": ["NODE.DC brand", "platform modularity", "source read-only before apply"]
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"id": "e.site.copy",
|
||||
"sourceType": "site_source",
|
||||
"ref": "/Users/dcconstructions/Downloads/mnt/NODEDC/NODEDC_SITE",
|
||||
"summary": "Current validation target for SEO module development."
|
||||
},
|
||||
{
|
||||
"id": "e.ops.card",
|
||||
"sourceType": "ops_card",
|
||||
"ref": "c2e5e655-693f-4da5-a83e-438e98a2f6df",
|
||||
"summary": "Operational SEO architecture card."
|
||||
},
|
||||
{
|
||||
"id": "e.ontology.core",
|
||||
"sourceType": "ontology_core",
|
||||
"ref": "platform/services/ontology-core",
|
||||
"summary": "Platform ontology service/module baseline."
|
||||
}
|
||||
],
|
||||
"approvals": {
|
||||
"status": "draft",
|
||||
"approvedBy": null,
|
||||
"approvedAt": null
|
||||
},
|
||||
"ontologyDeltaProposals": []
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import { readJson } from './catalog.mjs'
|
||||
import { loadCatalog, readJson } from './catalog.mjs'
|
||||
|
||||
const LAUNCHER_ROOT_GROUPS = new Set(['nodedc:superadmin', 'nodedc:launcher:admin'])
|
||||
const LAUNCHER_ROOT_ROLES = new Set(['root_admin'])
|
||||
const LAUNCHER_CLIENT_ADMIN_ROLES = new Set(['client_owner', 'client_admin'])
|
||||
|
||||
export async function loadAssistantAccessPolicy() {
|
||||
return readJson('catalog/assistant-access-policy.json')
|
||||
const catalog = await loadCatalog()
|
||||
return catalog.assistantAccessPolicy
|
||||
}
|
||||
|
||||
function normalizeString(value) {
|
||||
|
||||
@@ -6,6 +6,7 @@ const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
export const serviceRoot = path.resolve(__dirname, '..')
|
||||
export const catalogRoot = path.join(serviceRoot, 'catalog')
|
||||
export const domainPackagesRoot = path.join(catalogRoot, 'domain-packages')
|
||||
|
||||
export async function readJson(relativePath) {
|
||||
const fullPath = path.join(serviceRoot, relativePath)
|
||||
@@ -13,6 +14,229 @@ export async function readJson(relativePath) {
|
||||
return JSON.parse(raw)
|
||||
}
|
||||
|
||||
async function readJsonAt(fullPath) {
|
||||
const raw = await fs.readFile(fullPath, 'utf8')
|
||||
return JSON.parse(raw)
|
||||
}
|
||||
|
||||
async function readOptionalJsonAt(fullPath, fallback) {
|
||||
try {
|
||||
return await readJsonAt(fullPath)
|
||||
} catch (error) {
|
||||
if (error?.code === 'ENOENT') return fallback
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function listDomainPackageDirs() {
|
||||
try {
|
||||
const entries = await fs.readdir(domainPackagesRoot, { withFileTypes: true })
|
||||
return entries
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => path.join(domainPackagesRoot, entry.name))
|
||||
.sort()
|
||||
} catch (error) {
|
||||
if (error?.code === 'ENOENT') return []
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
function uniqueByValue(values) {
|
||||
return [...new Set(values)]
|
||||
}
|
||||
|
||||
function mergeRoleMatrix(baseMatrix = [], patchMatrix = []) {
|
||||
const byRole = new Map(baseMatrix.map((entry) => [entry.roleId, { ...entry }]))
|
||||
|
||||
for (const patch of patchMatrix || []) {
|
||||
const current = byRole.get(patch.roleId) || { roleId: patch.roleId }
|
||||
byRole.set(patch.roleId, {
|
||||
...current,
|
||||
...patch,
|
||||
allowedCapabilities: uniqueByValue([
|
||||
...(current.allowedCapabilities || []),
|
||||
...(patch.allowedCapabilities || []),
|
||||
]),
|
||||
deniedCapabilities: uniqueByValue([
|
||||
...(current.deniedCapabilities || []),
|
||||
...(patch.deniedCapabilities || []),
|
||||
]),
|
||||
deniedWhenNoAdminScope: uniqueByValue([
|
||||
...(current.deniedWhenNoAdminScope || []),
|
||||
...(patch.deniedWhenNoAdminScope || []),
|
||||
]),
|
||||
conditionalCapabilities: [
|
||||
...(current.conditionalCapabilities || []),
|
||||
...(patch.conditionalCapabilities || []),
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
return [...byRole.values()]
|
||||
}
|
||||
|
||||
function mergeAssistantAccessPolicy(base, patch = {}) {
|
||||
return {
|
||||
...base,
|
||||
roleVocabulary: [...(base.roleVocabulary || []), ...(patch.roleVocabulary || [])],
|
||||
roleAliases: [...(base.roleAliases || []), ...(patch.roleAliases || [])],
|
||||
sourceAuthorities: [...(base.sourceAuthorities || []), ...(patch.sourceAuthorities || [])],
|
||||
capabilities: [...(base.capabilities || []), ...(patch.capabilities || [])],
|
||||
roleMatrix: mergeRoleMatrix(base.roleMatrix, patch.roleMatrix),
|
||||
operations: [...(base.operations || []), ...(patch.operations || [])],
|
||||
hardDenies: [...(base.hardDenies || []), ...(patch.hardDenies || [])],
|
||||
}
|
||||
}
|
||||
|
||||
function mergeCatalog(base, extension) {
|
||||
const next = {
|
||||
...base,
|
||||
entities: {
|
||||
...base.entities,
|
||||
statusVocabulary: uniqueByValue([
|
||||
...(base.entities.statusVocabulary || []),
|
||||
...(extension.entities.statusVocabulary || []),
|
||||
]),
|
||||
entities: [...(base.entities.entities || []), ...(extension.entities.entities || [])],
|
||||
},
|
||||
relations: {
|
||||
...base.relations,
|
||||
relations: [...(base.relations.relations || []), ...(extension.relations.relations || [])],
|
||||
},
|
||||
aliases: {
|
||||
...base.aliases,
|
||||
aliases: [...(base.aliases.aliases || []), ...(extension.aliases.aliases || [])],
|
||||
},
|
||||
guardrails: {
|
||||
...base.guardrails,
|
||||
rules: [...(base.guardrails.rules || []), ...(extension.guardrails.rules || [])],
|
||||
blockedConflations: [
|
||||
...(base.guardrails.blockedConflations || []),
|
||||
...(extension.guardrails.blockedConflations || []),
|
||||
],
|
||||
},
|
||||
evidence: {
|
||||
...base.evidence,
|
||||
sourceRoots: [...(base.evidence.sourceRoots || []), ...(extension.evidence.sourceRoots || [])],
|
||||
ledgers: [...(base.evidence.ledgers || []), ...(extension.evidence.ledgers || [])],
|
||||
baselineDocs: [...(base.evidence.baselineDocs || []), ...(extension.evidence.baselineDocs || [])],
|
||||
restrictions: [...(base.evidence.restrictions || []), ...(extension.evidence.restrictions || [])],
|
||||
},
|
||||
resolverRules: {
|
||||
...base.resolverRules,
|
||||
rules: [...(base.resolverRules.rules || []), ...(extension.resolverRules.rules || [])],
|
||||
},
|
||||
assistantAccessPolicy: mergeAssistantAccessPolicy(
|
||||
base.assistantAccessPolicy,
|
||||
extension.assistantAccessPolicy,
|
||||
),
|
||||
assistantActions: {
|
||||
...base.assistantActions,
|
||||
apps: [...(base.assistantActions.apps || []), ...(extension.assistantActions.apps || [])],
|
||||
adapterStatuses: uniqueByValue([
|
||||
...(base.assistantActions.adapterStatuses || []),
|
||||
...(extension.assistantActions.adapterStatuses || []),
|
||||
]),
|
||||
actions: [...(base.assistantActions.actions || []), ...(extension.assistantActions.actions || [])],
|
||||
},
|
||||
assistantRiskPolicy: {
|
||||
...base.assistantRiskPolicy,
|
||||
riskLevels: [...(base.assistantRiskPolicy.riskLevels || []), ...(extension.assistantRiskPolicy.riskLevels || [])],
|
||||
confirmationModes: [
|
||||
...(base.assistantRiskPolicy.confirmationModes || []),
|
||||
...(extension.assistantRiskPolicy.confirmationModes || []),
|
||||
],
|
||||
selfActionPolicies: [
|
||||
...(base.assistantRiskPolicy.selfActionPolicies || []),
|
||||
...(extension.assistantRiskPolicy.selfActionPolicies || []),
|
||||
],
|
||||
hardDenies: [...(base.assistantRiskPolicy.hardDenies || []), ...(extension.assistantRiskPolicy.hardDenies || [])],
|
||||
globalRules: [
|
||||
...(base.assistantRiskPolicy.globalRules || []),
|
||||
...(extension.assistantRiskPolicy.globalRules || []),
|
||||
],
|
||||
},
|
||||
contextBindings: {
|
||||
...base.contextBindings,
|
||||
contextStatuses: uniqueByValue([
|
||||
...(base.contextBindings.contextStatuses || []),
|
||||
...(extension.contextBindings.contextStatuses || []),
|
||||
]),
|
||||
bindingStatuses: uniqueByValue([
|
||||
...(base.contextBindings.bindingStatuses || []),
|
||||
...(extension.contextBindings.bindingStatuses || []),
|
||||
]),
|
||||
contexts: [...(base.contextBindings.contexts || []), ...(extension.contextBindings.contexts || [])],
|
||||
bindingTypes: [
|
||||
...(base.contextBindings.bindingTypes || []),
|
||||
...(extension.contextBindings.bindingTypes || []),
|
||||
],
|
||||
bindings: [...(base.contextBindings.bindings || []), ...(extension.contextBindings.bindings || [])],
|
||||
},
|
||||
domainPackages: [...(base.domainPackages || []), ...(extension.domainPackages || [])],
|
||||
}
|
||||
|
||||
return next
|
||||
}
|
||||
|
||||
async function loadDomainPackage(packageDir) {
|
||||
const relativeDir = path.relative(serviceRoot, packageDir)
|
||||
const metadata = await readOptionalJsonAt(path.join(packageDir, 'package.json'), {})
|
||||
|
||||
return {
|
||||
entities: await readOptionalJsonAt(path.join(packageDir, 'entities.json'), { statusVocabulary: [], entities: [] }),
|
||||
relations: await readOptionalJsonAt(path.join(packageDir, 'relations.json'), { relations: [] }),
|
||||
aliases: await readOptionalJsonAt(path.join(packageDir, 'aliases.json'), { aliases: [] }),
|
||||
guardrails: await readOptionalJsonAt(path.join(packageDir, 'guardrails.json'), { rules: [], blockedConflations: [] }),
|
||||
evidence: await readOptionalJsonAt(path.join(packageDir, 'evidence.json'), {
|
||||
sourceRoots: [],
|
||||
ledgers: [],
|
||||
baselineDocs: [],
|
||||
restrictions: [],
|
||||
}),
|
||||
resolverRules: await readOptionalJsonAt(path.join(packageDir, 'resolver-rules.json'), { rules: [] }),
|
||||
assistantAccessPolicy: await readOptionalJsonAt(path.join(packageDir, 'assistant-access-policy.json'), {
|
||||
roleVocabulary: [],
|
||||
roleAliases: [],
|
||||
sourceAuthorities: [],
|
||||
capabilities: [],
|
||||
roleMatrix: [],
|
||||
operations: [],
|
||||
hardDenies: [],
|
||||
}),
|
||||
assistantActions: await readOptionalJsonAt(path.join(packageDir, 'assistant-actions.json'), {
|
||||
apps: [],
|
||||
adapterStatuses: [],
|
||||
actions: [],
|
||||
}),
|
||||
assistantRiskPolicy: await readOptionalJsonAt(path.join(packageDir, 'assistant-risk-policy.json'), {
|
||||
riskLevels: [],
|
||||
confirmationModes: [],
|
||||
selfActionPolicies: [],
|
||||
hardDenies: [],
|
||||
globalRules: [],
|
||||
}),
|
||||
contextBindings: await readOptionalJsonAt(path.join(packageDir, 'context-bindings.json'), {
|
||||
contextStatuses: [],
|
||||
bindingStatuses: [],
|
||||
contexts: [],
|
||||
bindingTypes: [],
|
||||
bindings: [],
|
||||
}),
|
||||
domainPackages: [{
|
||||
id: metadata.id || path.basename(packageDir),
|
||||
status: metadata.status || 'unknown',
|
||||
relativePath: relativeDir,
|
||||
summary: metadata.summary || '',
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDomainPackages() {
|
||||
const packageDirs = await listDomainPackageDirs()
|
||||
return Promise.all(packageDirs.map((packageDir) => loadDomainPackage(packageDir)))
|
||||
}
|
||||
|
||||
export async function loadCatalog() {
|
||||
const [
|
||||
entities,
|
||||
@@ -37,15 +261,8 @@ export async function loadCatalog() {
|
||||
])
|
||||
const contextBindings = await readJson('catalog/context-bindings.json')
|
||||
|
||||
const entityById = new Map(entities.entities.map((entity) => [entity.id, entity]))
|
||||
const relationById = new Map(relations.relations.map((relation) => [relation.id, relation]))
|
||||
const contextById = new Map(contextBindings.contexts.map((context) => [context.id, context]))
|
||||
const bindingTypeById = new Map(contextBindings.bindingTypes.map((bindingType) => [bindingType.id, bindingType]))
|
||||
const aliasByKey = new Map(
|
||||
aliases.aliases.map((alias) => [normalizeAlias(alias.alias), alias.canonicalId]),
|
||||
)
|
||||
|
||||
return {
|
||||
const domainPackages = await loadDomainPackages()
|
||||
const mergedCatalog = domainPackages.reduce((catalog, extension) => mergeCatalog(catalog, extension), {
|
||||
entities,
|
||||
relations,
|
||||
aliases,
|
||||
@@ -56,6 +273,19 @@ export async function loadCatalog() {
|
||||
assistantActions,
|
||||
assistantRiskPolicy,
|
||||
contextBindings,
|
||||
domainPackages: [],
|
||||
})
|
||||
|
||||
const entityById = new Map(mergedCatalog.entities.entities.map((entity) => [entity.id, entity]))
|
||||
const relationById = new Map(mergedCatalog.relations.relations.map((relation) => [relation.id, relation]))
|
||||
const contextById = new Map(mergedCatalog.contextBindings.contexts.map((context) => [context.id, context]))
|
||||
const bindingTypeById = new Map(mergedCatalog.contextBindings.bindingTypes.map((bindingType) => [bindingType.id, bindingType]))
|
||||
const aliasByKey = new Map(
|
||||
mergedCatalog.aliases.aliases.map((alias) => [normalizeAlias(alias.alias), alias.canonicalId]),
|
||||
)
|
||||
|
||||
return {
|
||||
...mergedCatalog,
|
||||
entityById,
|
||||
relationById,
|
||||
contextById,
|
||||
|
||||
@@ -349,6 +349,7 @@ export async function validateCatalog() {
|
||||
contexts: contextBindings.contexts.length,
|
||||
bindingTypes: contextBindings.bindingTypes.length,
|
||||
bindings: contextBindings.bindings.length,
|
||||
domainPackages: catalog.domainPackages.length,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user