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}
|
- \setkeys{Gin}{width=\linewidth,height=0.55\textheight,keepaspectratio}
|
||||||
- \renewcommand{\arraystretch}{1.3}
|
- \renewcommand{\arraystretch}{1.3}
|
||||||
- \hypersetup{pdfauthor={lda.chat}, pdftitle={Design and Implementation of lda.chat}}
|
- \hypersetup{pdfauthor={lda.chat}, pdftitle={Design and Implementation of lda.chat}}
|
||||||
|
diagram:
|
||||||
|
engine:
|
||||||
|
mermaid:
|
||||||
|
theme: neutral
|
||||||
---
|
---
|
||||||
|
|
||||||
# Introduction
|
# Introduction
|
||||||
@@ -635,7 +639,8 @@ call, remote OAuth, or provider quota.
|
|||||||
|
|
||||||
## Case Study Components
|
## 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
|
- `ops.py` --- a Python source exposing `read_notes`, `extract_report`, and
|
||||||
`render_markdown_report` as typed `NodeSpec` capabilities.
|
`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
|
## 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 |
|
| 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
|
`README.md`, and commands for config validation, server startup, capability
|
||||||
calls, draft/artifact/deployment creation, run, inspect, and trace.
|
calls, draft/artifact/deployment creation, run, inspect, and trace.
|
||||||
The runnable evidence bundle for this case study lives at
|
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
|
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
|
- Completed source provider docs: `docs/source_provider_guide.md` now covers
|
||||||
MCP HTTP, MCP stdio, Python sources, auth refs, OAuth refresh-token setup,
|
MCP HTTP, MCP stdio, Python sources, auth refs, OAuth refresh-token setup,
|
||||||
diagnostics, and the Google Drive MCP caveat.
|
diagnostics, and the Google Drive MCP caveat.
|
||||||
- Completed platform source policy: `wf.*` process-provided sources are marked
|
- Completed platform source policy: documented fixed-id sources such as `wf.std`
|
||||||
as platform sources. They resolve by fixed source id, do not require
|
and `wf.source` are platform sources. They resolve by fixed source id, do not
|
||||||
self-bindings, and deployment validation rejects explicit platform-source
|
require self-bindings, and deployment validation rejects explicit
|
||||||
bindings as stale configuration.
|
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
|
- Completed `wf.source.read_resource`: resource refs are inert pass-by-value
|
||||||
data using `logical_source`; explicit platform helper nodes dereference them
|
data using `logical_source`; explicit platform helper nodes dereference them
|
||||||
through runtime/platform context with bounded output.
|
through runtime/platform context with bounded output.
|
||||||
|
|||||||
+4
-2
@@ -72,8 +72,10 @@ see [`workflow platform presentation`](add/2026-06-workflow-platform-presentatio
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- `docs/add/system-design-implementation.md` — formal thesis/system-design draft.
|
- [`docs/add/system-design-implementation.md`](add/system-design-implementation.md)
|
||||||
- `docs/add/evidence-index.md` — claim-to-evidence map for the thesis draft.
|
— formal thesis/system-design draft.
|
||||||
|
- [`docs/add/evidence-index.md`](add/evidence-index.md) — claim-to-evidence
|
||||||
|
map for the thesis draft.
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ from pydantic import BaseModel, Field
|
|||||||
|
|
||||||
from wf_authoring import node
|
from wf_authoring import node
|
||||||
|
|
||||||
|
_EXAMPLE_DIR = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
|
||||||
class ReadInput(BaseModel):
|
class ReadInput(BaseModel):
|
||||||
path: str = Field(description="Path to a UTF-8 Markdown notes file.")
|
path: str = Field(description="Path to a UTF-8 Markdown notes file.")
|
||||||
@@ -43,11 +45,25 @@ class MarkdownOutput(BaseModel):
|
|||||||
|
|
||||||
@node(name="read_notes")
|
@node(name="read_notes")
|
||||||
def read_notes(payload: ReadInput) -> ReadOutput:
|
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")
|
@node(name="extract_report")
|
||||||
def extract_report(payload: ExtractInput) -> ReportOutput:
|
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 = ""
|
title = ""
|
||||||
summary_lines: list[str] = []
|
summary_lines: list[str] = []
|
||||||
actions: list[ActionItem] = []
|
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
|
report = payload.report
|
||||||
lines = [
|
lines = [
|
||||||
f"# {report.title}",
|
f"# {report.title}",
|
||||||
"",
|
"",
|
||||||
|
"Summary:",
|
||||||
report.summary,
|
report.summary,
|
||||||
"",
|
"",
|
||||||
"## Action Items",
|
"Actions:",
|
||||||
]
|
]
|
||||||
lines.extend(
|
lines.extend(
|
||||||
f"- {item.owner}: {item.task} (due: {item.due})"
|
f"- {item.owner} | {item.task} | {item.due}"
|
||||||
for item in report.action_items
|
for item in report.action_items
|
||||||
)
|
)
|
||||||
lines.extend(["", "## Risks"])
|
lines.extend(["", "Risks:"])
|
||||||
lines.extend(f"- {risk}" for risk in report.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)
|
lines.extend(f"- {followup}" for followup in report.followups)
|
||||||
return MarkdownOutput(markdown="\n".join(lines))
|
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]
|
registry = [read_notes, extract_report, render_markdown_report]
|
||||||
|
|||||||
@@ -1,24 +1,32 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[2]
|
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:
|
def test_big_doc_links_case_study_and_evidence_index() -> None:
|
||||||
doc = (ROOT / "docs" / "add" / "system-design-implementation.md").read_text(
|
doc = (ROOT / "docs" / "add" / "system-design-implementation.md").read_text(
|
||||||
encoding="utf-8"
|
encoding="utf-8"
|
||||||
)
|
)
|
||||||
|
links = markdown_links(doc)
|
||||||
|
|
||||||
assert "examples/report_workflow" in doc
|
assert "evidence-index.md" in links
|
||||||
assert "docs/add/evidence-index.md" in doc or "evidence-index.md" in doc
|
assert any(link.startswith("../../examples/report_workflow") for link in links)
|
||||||
|
|
||||||
|
|
||||||
def test_project_map_links_big_doc() -> None:
|
def test_project_map_links_big_doc() -> None:
|
||||||
project_map = (ROOT / "docs" / "project_map.md").read_text(encoding="utf-8")
|
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 any(link.endswith("system-design-implementation.md") for link in links)
|
||||||
assert "evidence-index.md" in project_map
|
assert any(link.endswith("evidence-index.md") for link in links)
|
||||||
|
|
||||||
|
|
||||||
def test_big_doc_keeps_mcp_as_source_family() -> None:
|
def test_big_doc_keeps_mcp_as_source_family() -> None:
|
||||||
|
|||||||
@@ -4,12 +4,44 @@ from pathlib import Path
|
|||||||
|
|
||||||
import pytest
|
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_config import load_workflow_config
|
||||||
from wf_server.config import build_workflow_server_from_workflow_config
|
from wf_server.config import build_workflow_server_from_workflow_config
|
||||||
|
|
||||||
EXAMPLE_DIR = Path(__file__).resolve().parents[2] / "examples" / "report_workflow"
|
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
|
@pytest.mark.asyncio
|
||||||
async def test_report_workflow_python_source_loads_and_calls_capability(
|
async def test_report_workflow_python_source_loads_and_calls_capability(
|
||||||
tmp_path,
|
tmp_path,
|
||||||
|
|||||||
Reference in New Issue
Block a user