docs: address thesis review nits
This commit is contained in:
@@ -46,6 +46,10 @@ header-includes:
|
||||
- \setkeys{Gin}{width=\linewidth,height=0.55\textheight,keepaspectratio}
|
||||
- \renewcommand{\arraystretch}{1.3}
|
||||
- \hypersetup{pdfauthor={lda.chat}, pdftitle={Design and Implementation of lda.chat}}
|
||||
diagram:
|
||||
engine:
|
||||
mermaid:
|
||||
theme: neutral
|
||||
---
|
||||
|
||||
# Introduction
|
||||
@@ -635,7 +639,8 @@ call, remote OAuth, or provider quota.
|
||||
|
||||
## Case Study Components
|
||||
|
||||
The example bundle lives at `examples/report_workflow/` and contains:
|
||||
The example bundle lives at
|
||||
[`examples/report_workflow/`](../../examples/report_workflow/) and contains:
|
||||
|
||||
- `ops.py` --- a Python source exposing `read_notes`, `extract_report`, and
|
||||
`render_markdown_report` as typed `NodeSpec` capabilities.
|
||||
@@ -1170,7 +1175,7 @@ uv run wf --config examples/report_workflow/wf.config.json run trace <run_id> --
|
||||
|
||||
## Appendix B: Evidence Index
|
||||
|
||||
See `evidence-index.md` for the full claim-to-evidence map.
|
||||
See [evidence-index.md](evidence-index.md) for the full claim-to-evidence map.
|
||||
|
||||
| Claim | Evidence |
|
||||
| --- | --- |
|
||||
|
||||
@@ -64,7 +64,7 @@ The case study should exist as a runnable example, not only prose. Target shape:
|
||||
`README.md`, and commands for config validation, server startup, capability
|
||||
calls, draft/artifact/deployment creation, run, inspect, and trace.
|
||||
The runnable evidence bundle for this case study lives at
|
||||
`examples/report_workflow/README.md`.
|
||||
`examples/report_workflow/`.
|
||||
|
||||
|
||||
Keep the thesis-critical path deterministic. Do not require an LLM call inside
|
||||
|
||||
@@ -113,10 +113,11 @@ auth admin are implemented. The next work is polish, not new broad surfaces.
|
||||
- Completed source provider docs: `docs/source_provider_guide.md` now covers
|
||||
MCP HTTP, MCP stdio, Python sources, auth refs, OAuth refresh-token setup,
|
||||
diagnostics, and the Google Drive MCP caveat.
|
||||
- Completed platform source policy: `wf.*` process-provided sources are marked
|
||||
as platform sources. They resolve by fixed source id, do not require
|
||||
self-bindings, and deployment validation rejects explicit platform-source
|
||||
bindings as stale configuration.
|
||||
- Completed platform source policy: documented fixed-id sources such as `wf.std`
|
||||
and `wf.source` are platform sources. They resolve by fixed source id, do not
|
||||
require self-bindings, and deployment validation rejects explicit
|
||||
platform-source bindings as stale configuration. Other `wf.*` namespaces are
|
||||
described by their own source docs/policies.
|
||||
- Completed `wf.source.read_resource`: resource refs are inert pass-by-value
|
||||
data using `logical_source`; explicit platform helper nodes dereference them
|
||||
through runtime/platform context with bounded output.
|
||||
|
||||
+4
-2
@@ -72,8 +72,10 @@ see [`workflow platform presentation`](add/2026-06-workflow-platform-presentatio
|
||||
|
||||
## Documentation
|
||||
|
||||
- `docs/add/system-design-implementation.md` — formal thesis/system-design draft.
|
||||
- `docs/add/evidence-index.md` — claim-to-evidence map for the thesis draft.
|
||||
- [`docs/add/system-design-implementation.md`](add/system-design-implementation.md)
|
||||
— formal thesis/system-design draft.
|
||||
- [`docs/add/evidence-index.md`](add/evidence-index.md) — claim-to-evidence
|
||||
map for the thesis draft.
|
||||
|
||||
## Tests
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ from pydantic import BaseModel, Field
|
||||
|
||||
from wf_authoring import node
|
||||
|
||||
_EXAMPLE_DIR = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
class ReadInput(BaseModel):
|
||||
path: str = Field(description="Path to a UTF-8 Markdown notes file.")
|
||||
@@ -43,11 +45,25 @@ class MarkdownOutput(BaseModel):
|
||||
|
||||
@node(name="read_notes")
|
||||
def read_notes(payload: ReadInput) -> ReadOutput:
|
||||
return ReadOutput(text=Path(payload.path).read_text(encoding="utf-8"))
|
||||
return _read_notes(payload)
|
||||
|
||||
|
||||
@node(name="extract_report")
|
||||
def extract_report(payload: ExtractInput) -> ReportOutput:
|
||||
return _extract_report(payload)
|
||||
|
||||
|
||||
@node(name="render_markdown_report")
|
||||
def render_markdown_report(payload: MarkdownInput) -> MarkdownOutput:
|
||||
return _render_markdown_report(payload)
|
||||
|
||||
|
||||
def _read_notes(payload: ReadInput) -> ReadOutput:
|
||||
path = _resolve_example_path(payload.path)
|
||||
return ReadOutput(text=path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _extract_report(payload: ExtractInput) -> ReportOutput:
|
||||
title = ""
|
||||
summary_lines: list[str] = []
|
||||
actions: list[ActionItem] = []
|
||||
@@ -86,25 +102,36 @@ def extract_report(payload: ExtractInput) -> ReportOutput:
|
||||
)
|
||||
|
||||
|
||||
@node(name="render_markdown_report")
|
||||
def render_markdown_report(payload: MarkdownInput) -> MarkdownOutput:
|
||||
def _render_markdown_report(payload: MarkdownInput) -> MarkdownOutput:
|
||||
report = payload.report
|
||||
lines = [
|
||||
f"# {report.title}",
|
||||
"",
|
||||
"Summary:",
|
||||
report.summary,
|
||||
"",
|
||||
"## Action Items",
|
||||
"Actions:",
|
||||
]
|
||||
lines.extend(
|
||||
f"- {item.owner}: {item.task} (due: {item.due})"
|
||||
f"- {item.owner} | {item.task} | {item.due}"
|
||||
for item in report.action_items
|
||||
)
|
||||
lines.extend(["", "## Risks"])
|
||||
lines.extend(["", "Risks:"])
|
||||
lines.extend(f"- {risk}" for risk in report.risks)
|
||||
lines.extend(["", "## Followups"])
|
||||
lines.extend(["", "Followups:"])
|
||||
lines.extend(f"- {followup}" for followup in report.followups)
|
||||
return MarkdownOutput(markdown="\n".join(lines))
|
||||
|
||||
|
||||
def _resolve_example_path(path: str) -> Path:
|
||||
"""Resolve user input to a file inside this example directory only."""
|
||||
candidate = Path(path)
|
||||
if candidate.is_absolute():
|
||||
raise ValueError("read_notes only accepts paths relative to the example")
|
||||
resolved = (_EXAMPLE_DIR / candidate).resolve()
|
||||
if not resolved.is_relative_to(_EXAMPLE_DIR):
|
||||
raise ValueError("read_notes path must stay inside the example directory")
|
||||
return resolved
|
||||
|
||||
|
||||
registry = [read_notes, extract_report, render_markdown_report]
|
||||
|
||||
@@ -1,24 +1,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def markdown_links(text: str) -> set[str]:
|
||||
"""Return inline Markdown link hrefs from docs smoke-test files."""
|
||||
return set(re.findall(r"(?<!!)\[[^\]]+\]\(([^)]+)\)", text))
|
||||
|
||||
|
||||
def test_big_doc_links_case_study_and_evidence_index() -> None:
|
||||
doc = (ROOT / "docs" / "add" / "system-design-implementation.md").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
links = markdown_links(doc)
|
||||
|
||||
assert "examples/report_workflow" in doc
|
||||
assert "docs/add/evidence-index.md" in doc or "evidence-index.md" in doc
|
||||
assert "evidence-index.md" in links
|
||||
assert any(link.startswith("../../examples/report_workflow") for link in links)
|
||||
|
||||
|
||||
def test_project_map_links_big_doc() -> None:
|
||||
project_map = (ROOT / "docs" / "project_map.md").read_text(encoding="utf-8")
|
||||
links = markdown_links(project_map)
|
||||
|
||||
assert "system-design-implementation.md" in project_map
|
||||
assert "evidence-index.md" in project_map
|
||||
assert any(link.endswith("system-design-implementation.md") for link in links)
|
||||
assert any(link.endswith("evidence-index.md") for link in links)
|
||||
|
||||
|
||||
def test_big_doc_keeps_mcp_as_source_family() -> None:
|
||||
|
||||
@@ -4,12 +4,44 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from examples.report_workflow.ops import (
|
||||
ActionItem,
|
||||
ExtractInput,
|
||||
MarkdownInput,
|
||||
ReadInput,
|
||||
ReportOutput,
|
||||
_extract_report,
|
||||
_read_notes,
|
||||
_render_markdown_report,
|
||||
)
|
||||
from wf_config import load_workflow_config
|
||||
from wf_server.config import build_workflow_server_from_workflow_config
|
||||
|
||||
EXAMPLE_DIR = Path(__file__).resolve().parents[2] / "examples" / "report_workflow"
|
||||
|
||||
|
||||
def test_report_workflow_read_notes_rejects_paths_outside_example() -> None:
|
||||
with pytest.raises(ValueError, match="inside the example"):
|
||||
_read_notes(ReadInput(path="../pyproject.toml"))
|
||||
|
||||
|
||||
def test_report_workflow_markdown_renderer_round_trips_through_extractor() -> None:
|
||||
report = ReportOutput(
|
||||
title="Weekly Project Update",
|
||||
summary="Demo summary.",
|
||||
action_items=[
|
||||
ActionItem(owner="Alice", task="Prepare demo config", due="Friday")
|
||||
],
|
||||
risks=["Quota is low"],
|
||||
followups=["Render Markdown"],
|
||||
)
|
||||
|
||||
rendered = _render_markdown_report(MarkdownInput(report=report))
|
||||
extracted = _extract_report(ExtractInput(text=rendered.markdown))
|
||||
|
||||
assert extracted == report
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_report_workflow_python_source_loads_and_calls_capability(
|
||||
tmp_path,
|
||||
|
||||
Reference in New Issue
Block a user