1
0
Fork 0
Code Issues Pull requests Projects Releases 2 Packages Wiki Activity Actions Pages

Add versioned independent projection contracts

This commit is contained in:
Andraxion 2026-07-29 10:50:05 -04:00
parent 4c5773c865
commit 96e3965855
22 changed files with 3561 additions and 133 deletions

View file

@ -0,0 +1 @@
"""Capability-isolated renderer implementations for DocForge projection packages."""

View file

@ -0,0 +1,172 @@
"""Plan-only renderer for the built-in DocForge manual artifact."""
from __future__ import annotations
import hashlib
import html
import re
from time import perf_counter_ns
from typing import cast
from markdown_it import MarkdownIt
from docforge.errors import DocForgeError
from docforge.projection_contract import (
ProjectionArtifact,
ProjectionPackageV1,
ProjectionReceiptV1,
ProjectionRenderResult,
)
_TEMPLATE_TOKEN = re.compile(r"{{\s*([a-z_][a-z0-9_]*)\s*}}")
_ALLOWED_TOKENS = frozenset(
{
"docforge_content",
"docforge_project_id",
"docforge_render_identity",
"docforge_title",
"docforge_view_id",
}
)
_ACTIVE_TEMPLATE_CONTENT = re.compile(
r"<\s*(?:script|iframe|object|embed)\b"
r"|\son[a-z0-9_-]+\s*="
r"|javascript\s*:"
r"|<\s*meta\b[^>]*\bhttp-equiv\s*=\s*[\"']?\s*refresh\b",
re.IGNORECASE,
)
class ManualHtmlRenderer:
"""Transform one validated path-free package without graph-selection authority."""
renderer_id = "generic_html"
def __init__(self, renderer_version: str) -> None:
self.renderer_version = renderer_version
self.markdown = MarkdownIt("commonmark", {"html": False, "typographer": False})
def render(
self,
package: ProjectionPackageV1,
*,
render_identity: str | None = None,
) -> ProjectionRenderResult:
started = perf_counter_ns()
package = ProjectionPackageV1.from_dict(package.as_dict())
document = package.document
if package.kind != "manual":
raise DocForgeError("invalid_projection", "Manual renderer requires a manual package")
renderer = cast(dict[str, object], document["renderer"])
if renderer != {
"renderer_id": self.renderer_id,
"renderer_version": self.renderer_version,
}:
raise DocForgeError("unsupported_renderer", "Manual renderer identity is incompatible")
plan = cast(dict[str, object], document["plan"])
assets = cast(list[object], document["assets"])
if len(assets) != 1 or not isinstance(assets[0], dict):
raise DocForgeError("invalid_projection", "Manual template asset is invalid")
asset = cast(dict[str, object], assets[0])
if (
set(asset) != {"asset_id", "media_type", "sha256", "text"}
or asset.get("asset_id") != "manual.template"
or asset.get("media_type") != "text/html; charset=utf-8"
or not isinstance(asset.get("text"), str)
):
raise DocForgeError("invalid_projection", "Manual template asset is invalid")
template = cast(str, asset["text"])
template_bytes = template.encode("utf-8")
if hashlib.sha256(template_bytes).hexdigest() != asset.get("sha256"):
raise DocForgeError("invalid_projection", "Manual template asset hash is invalid")
if _ACTIVE_TEMPLATE_CONTENT.search(template):
raise DocForgeError(
"invalid_template",
"Render template contains active or executable content",
)
tokens = _TEMPLATE_TOKEN.findall(template)
unknown = sorted(set(tokens) - _ALLOWED_TOKENS)
remainder = _TEMPLATE_TOKEN.sub("", template)
if unknown or "{{" in remainder or "}}" in remainder:
raise DocForgeError(
"invalid_template",
"Render template contains unsupported tokens",
tokens=unknown,
)
if tokens.count("docforge_content") != 1:
raise DocForgeError(
"invalid_template",
"Render template must contain docforge_content exactly once",
)
identity = render_identity or cast(str, plan["plan_id"])
project = cast(dict[str, object], plan["project"])
view = cast(dict[str, object], plan["view"])
replacements = {
"docforge_content": self._content(plan),
"docforge_project_id": html.escape(cast(str, project["project_id"]), quote=True),
"docforge_render_identity": identity,
"docforge_title": html.escape(cast(str, view["title"]), quote=True),
"docforge_view_id": html.escape(cast(str, view["view_id"]), quote=True),
}
rendered = _TEMPLATE_TOKEN.sub(lambda match: replacements[match.group(1)], template)
output = rendered.rstrip().encode("utf-8") + b"\n"
policy = cast(dict[str, object], document["output_policy"])
maximum = policy.get("max_total_bytes")
if type(maximum) is not int or maximum < 1 or len(output) > maximum:
raise DocForgeError("render_too_large", "Rendered output exceeds the configured limit")
artifact = ProjectionArtifact(
artifact_id="manual.html",
media_type="text/html; charset=utf-8",
content=output,
)
receipt = ProjectionReceiptV1.create(
kind="manual",
package_id=package.package_id,
plan_id=cast(str, document["plan_id"]),
renderer=dict(renderer),
artifacts=[artifact.evidence()],
diagnostics={"warnings": []},
timing={"elapsed_ns": perf_counter_ns() - started},
peak_memory_bytes=None,
)
return ProjectionRenderResult((artifact,), receipt)
def _content(self, plan: dict[str, object]) -> str:
navigation = ['<nav aria-label="Documentation"><ul>']
for value in cast(list[object], plan["navigation"]):
item = cast(dict[str, object], value)
navigation.append(
f'<li><a href="#node-{html.escape(cast(str, item["node_id"]), quote=True)}">'
f"{html.escape(cast(str, item['title']))}</a></li>"
)
navigation.append("</ul></nav>")
sections = [*navigation]
for value in cast(list[object], plan["pages"]):
page = cast(dict[str, object], value)
node_id = cast(str, page["node_id"])
sections.extend(
[
f'<section id="node-{html.escape(node_id, quote=True)}">',
f"<h2>{html.escape(cast(str, page['title']))}</h2>",
'<dl class="docforge-node-meta">',
f"<dt>ID</dt><dd>{html.escape(node_id)}</dd>",
f"<dt>Family</dt><dd>{html.escape(cast(str, page['family']))}</dd>",
f"<dt>Status</dt><dd>{html.escape(cast(str, page['status']))}</dd>",
f"<dt>Authority</dt><dd>{html.escape(cast(str, page['authority']))}</dd>",
"</dl>",
f'<p class="docforge-summary">{html.escape(cast(str, page["summary"]))}</p>',
self.markdown.render(cast(str, page["content"])).rstrip(),
]
)
relationships = cast(list[object], page["cross_references"])
if relationships:
sections.append('<ul class="docforge-relationships">')
for relationship_value in relationships:
relationship = cast(dict[str, object], relationship_value)
sections.append(
f"<li>{html.escape(cast(str, relationship['relation']))}: "
f"{html.escape(cast(str, relationship['target_id']))}</li>"
)
sections.append("</ul>")
sections.append("</section>")
return "\n".join(sections)

View file

@ -0,0 +1 @@
# PEP 561 marker for the typed DocForge renderer package.