Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions dev-docs/adr/ADR-0006-public-edit-api-and-host-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# ADR-0006: Public Edit API and Host-Owned Safety Boundary

## Status

`accepted`

## Background

ExStruct already had a capable workbook editing pipeline, but it was reachable
primarily through MCP-facing modules such as `exstruct.mcp.patch_runner` and the
`exstruct_patch` / `exstruct_make` tool handlers. That made normal Python usage
awkward and blurred the boundary between editing behavior and host-owned safety
policy.

The issue is not only discoverability. The MCP layer also owns path sandboxing,
artifact mirroring, and server execution concerns that should not be mandatory
for ordinary library callers. At the same time, existing patch models, backend
selection rules, and warning/error payloads are already tested and should remain
stable while the public surface is promoted.

## Decision

- `exstruct.edit` is adopted as the first-class public Python API for workbook
editing.
- Phase 1 public entry points are `patch_workbook(PatchRequest)` and
`make_workbook(MakeRequest)`.
- The existing patch contract is preserved for Phase 1:
- `PatchOp`, `PatchRequest`, `MakeRequest`, `PatchResult`
- op names
- normalization behavior
- schema-discovery metadata
- backend warning/error payload shape
- MCP remains a host/integration layer. It continues to own:
- `PathPolicy`
- MCP tool input/output mapping
- artifact mirroring
- server defaults, thread offloading, and runtime controls
- Phase 1 may reuse the proven `exstruct.mcp.patch.*` execution pipeline under
the hood while `exstruct.edit` becomes the canonical public import path.

## Consequences

- Python callers now have a direct, library-oriented entry point that does not
require MCP-specific path restrictions.
- MCP compatibility remains intact because the old import paths stay available.
- Operation schemas and alias normalization can now be treated as part of the
public editing surface, not only MCP documentation.
- The transition keeps two module trees in play during Phase 1, which is less
clean than a full implementation relocation but materially reduces risk while
the new public surface is established.
- Future phases can move more execution internals under `exstruct.edit` without
reopening the public contract question.

## Rationale

- Tests:
- `tests/edit/test_api.py`
- `tests/mcp/patch/test_normalize.py`
- `tests/mcp/test_patch_runner.py`
- `tests/mcp/test_make_runner.py`
- `tests/mcp/patch/test_service.py`
- `tests/mcp/test_tools_handlers.py`
- Code:
- `src/exstruct/edit/__init__.py`
- `src/exstruct/edit/api.py`
- `src/exstruct/edit/service.py`
- `src/exstruct/mcp/patch_runner.py`
- `src/exstruct/mcp/tools.py`
- Related specs:
- `dev-docs/specs/editing-api.md`
- `dev-docs/specs/data-model.md`
- `docs/api.md`
- `docs/mcp.md`

## Supersedes

- None

## Superseded by

- None
1 change: 1 addition & 0 deletions dev-docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ ADRs record what was decided, under which constraints, and which trade-offs were
| `ADR-0003` | Output Serialization Omission Policy | `accepted` | `schema` |
| `ADR-0004` | Patch Backend Selection Policy | `accepted` | `mcp` |
| `ADR-0005` | PathPolicy Safety Boundary | `accepted` | `safety` |
| `ADR-0006` | Public Edit API and Host-Owned Safety Boundary | `accepted` | `editing` |
10 changes: 10 additions & 0 deletions dev-docs/adr/decision-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,20 @@ This document is a human-readable map for navigating ADRs by domain.

- `ADR-0004` Patch Backend Selection Policy (`accepted`)
- `ADR-0005` PathPolicy Safety Boundary (`accepted`)
- `ADR-0006` Public Edit API and Host-Owned Safety Boundary (`accepted`)

## safety

- `ADR-0005` PathPolicy Safety Boundary (`accepted`)
- `ADR-0006` Public Edit API and Host-Owned Safety Boundary (`accepted`)

## editing

- `ADR-0006` Public Edit API and Host-Owned Safety Boundary (`accepted`)

## api

- `ADR-0006` Public Edit API and Host-Owned Safety Boundary (`accepted`)

## Supersession Relationships

Expand Down
17 changes: 17 additions & 0 deletions dev-docs/adr/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,20 @@ adrs:
superseded_by: []
related_specs:
- docs/mcp.md
- id: ADR-0006
title: Public Edit API and Host-Owned Safety Boundary
status: accepted
path: dev-docs/adr/ADR-0006-public-edit-api-and-host-boundary.md
primary_domain: editing
domains:
- editing
- api
- mcp
- safety
supersedes: []
superseded_by: []
related_specs:
- dev-docs/specs/editing-api.md
- dev-docs/specs/data-model.md
- docs/api.md
- docs/mcp.md
30 changes: 29 additions & 1 deletion dev-docs/architecture/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ exstruct/
io/
serialize.py
render/
edit/
__init__.py
api.py
chart_types.py
errors.py
service.py
models.py
normalize.py
op_schema.py
specs.py
types.py
cli/
main.py
```
Expand Down Expand Up @@ -73,9 +84,18 @@ PDF/PNG output (for RAG use cases)

CLI entry point

### edit/

First-class public workbook editing API

- `api.py` / `service.py` → public patch/make entry points for Python callers
- `models.py` → public edit request/result models
- `normalize.py` / `specs.py` / `op_schema.py` → public patch-op normalization and schema metadata
- Phase 1 keeps the proven backend execution under `mcp/patch/*` while `edit/` becomes the canonical public import path

### mcp/patch (Patch Implementation)

Patch functionality is responsibility-separated under `src/exstruct/mcp/patch/`.
MCP editing remains the integration layer around the public edit API.

- `patch_runner.py` → compatibility facade for maintaining existing import paths
- `patch/internal.py` → internal compatibility layer for patch implementation (non-public)
Expand All @@ -88,6 +108,14 @@ Patch functionality is responsibility-separated under `src/exstruct/mcp/patch/`.
- `patch/normalize.py` / `patch/specs.py` → op normalization and spec metadata
- `shared/a1.py` / `shared/output_path.py` → shared utilities for A1 notation and output paths

### mcp/

Host-layer responsibilities for MCP and agent tooling

- `io.py` → `PathPolicy` sandbox boundary
- `tools.py` / `server.py` → tool transport, artifact mirroring, runtime defaults, and thread offloading
- The MCP layer keeps safety policy and host behavior out of the public Python editing API

---

## Guide for AI Agents
Expand Down
14 changes: 8 additions & 6 deletions dev-docs/specs/data-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,16 +258,18 @@ Common:

---

# Appendix A. MCP Patch Models
# Appendix A. Editing Models

The model group used by MCP patch/make operations remains importable from
`exstruct.mcp.patch_runner` for backward compatibility.
The model group used by workbook editing remains importable from both
`exstruct.edit` and `exstruct.mcp.patch_runner` for backward compatibility.

The actual locations are as follows.

- Canonical models: `src/exstruct/mcp/patch/models.py`
- Compatibility facade: `src/exstruct/mcp/patch_runner.py`
- Service layer: `src/exstruct/mcp/patch/service.py`
- Primary public import path: `exstruct.edit` / `exstruct.edit.models`
- Current backing implementation module: `exstruct.mcp.patch.models`
- Compatibility facade import path: `exstruct.mcp.patch_runner`
- Public service layer import path: `exstruct.edit.service`
- MCP integration layer import path: `exstruct.mcp.patch.service`

Primary models:

Expand Down
68 changes: 68 additions & 0 deletions dev-docs/specs/editing-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Editing API Specification

This document defines the Phase 1 public editing contract exposed from
`exstruct.edit`.

## Public import path

- Primary public package: `exstruct.edit`
- Primary functions:
- `patch_workbook(request: PatchRequest) -> PatchResult`
- `make_workbook(request: MakeRequest) -> PatchResult`
- Primary public models:
- `PatchOp`
- `PatchRequest`
- `MakeRequest`
- `PatchResult`
- `PatchDiffItem`
- `PatchErrorDetail`
- `FormulaIssue`

## Phase 1 guarantees

- Python callers can edit workbooks through `exstruct.edit` without providing
MCP-specific `PathPolicy` restrictions.
- The patch operation vocabulary, field names, defaults, warnings, and error
payload shapes remain aligned with the existing MCP patch contract.
- `exstruct.edit` exposes the same operation normalization and schema metadata
used by MCP:
- `coerce_patch_ops`
- `resolve_top_level_sheet_for_payload`
- `list_patch_op_schemas`
- `get_patch_op_schema`
- Existing MCP compatibility imports remain valid:
- `exstruct.mcp.patch_runner`
- `exstruct.mcp.patch.normalize`
- `exstruct.mcp.patch.specs`
- `exstruct.mcp.op_schema`

## Host-only responsibilities

The following behaviors are not part of the Python editing API contract and
remain owned by MCP / agent hosts:

- `PathPolicy` root restrictions and deny-glob enforcement
- MCP tool input/output models and transport mapping
- artifact mirroring for MCP hosts
- server-level defaults such as `--on-conflict`
- thread offloading, timeouts, and confirmation flows

## Current implementation boundary

- Phase 1 promotes `exstruct.edit` as the canonical public import path.
- The implementation intentionally reuses the existing patch execution pipeline
under `exstruct.mcp.patch.*` to avoid destabilizing the tested backend logic
during the API promotion.
- Contract metadata moved to `exstruct.edit` in Phase 1:
- patch op types
- chart type metadata
- patch op alias/spec metadata
- public op schema discovery

## Explicit non-goals for Phase 1

- No top-level `from exstruct import patch_workbook` export
- No new CLI subcommands
- No op renaming (`set_value` remains the public op name)
- No change to backend selection or fallback policy
- No change to `PatchResult` shape
46 changes: 46 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ This page shows the primary APIs, minimal runnable examples, expected outputs, a
- [API Reference](#api-reference)
- [TOC](#toc)
- [Quick Examples](#quick-examples)
- [Editing API](#editing-api)
- [Dependencies](#dependencies)
- [Auto-generated API mkdocstrings](#auto-generated-api-mkdocstrings)
- [Core functions](#core-functions)
- [Editing functions](#editing-functions)
- [Engine and options](#engine-and-options)
- [Models](#models)
- [Model helpers SheetData / WorkbookData](#model-helpers-sheetdata--workbookdata)
Expand Down Expand Up @@ -74,6 +76,36 @@ process_excel(
# Same as: exstruct input.xlsx --format json --include-backend-metadata --pdf --image --mode standard --pretty --sheets-dir out_sheets > out.json
```

## Editing API

Phase 1 exposes workbook editing as a first-class Python package under
`exstruct.edit`.

```python
from pathlib import Path

from exstruct.edit import PatchOp, PatchRequest, patch_workbook

result = patch_workbook(
PatchRequest(
xlsx_path=Path("book.xlsx"),
ops=[PatchOp(op="set_value", sheet="Sheet1", cell="A1", value="updated")],
backend="openpyxl",
)
)

print(result.out_path)
print(result.engine)
```

Key points:

- `exstruct.edit` does not require MCP `PathPolicy`.
- `PatchOp`, `PatchRequest`, `MakeRequest`, and `PatchResult` keep the existing
MCP patch contract in Phase 1.
- Use `list_patch_op_schemas()` / `get_patch_op_schema()` to inspect the public
operation schema programmatically.

## Dependencies

- Core extraction: pandas, openpyxl (installed with the package).
Expand Down Expand Up @@ -142,6 +174,20 @@ Python APIの最新情報は以下の自動生成セクションを参照して
show_signature_annotations: true
show_root_heading: true

### Editing functions

::: exstruct.edit.patch_workbook
handler: python
options:
show_signature_annotations: true
show_root_heading: true

::: exstruct.edit.make_workbook
handler: python
options:
show_signature_annotations: true
show_root_heading: true

### Engine and options

::: exstruct.engine.ExStructEngine
Expand Down
8 changes: 7 additions & 1 deletion docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,15 +297,21 @@ Example:

### Internal implementation note

Workbook editing now also has a public Python import path under `exstruct.edit`.
MCP remains the host/integration layer and keeps path policy plus transport
concerns outside that public API.

The patch implementation is layered to keep compatibility while enabling refactoring:

- `exstruct.edit`: first-class Python editing API
- `exstruct.mcp.patch_runner`: compatibility facade (existing import path)
- `exstruct.mcp.patch.service`: patch/make orchestration
- `exstruct.mcp.patch.engine.*`: backend execution boundaries (openpyxl/com)
- `exstruct.mcp.patch.runtime`: runtime utilities (path/backend selection)
- `exstruct.mcp.patch.ops.*`: backend-specific op application entrypoints

This keeps MCP tool I/O stable while allowing internal module separation.
This keeps MCP tool I/O stable while allowing the Python API and host policy to
evolve independently.

## Edit flow (patch)

Expand Down
Loading
Loading