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
40 changes: 40 additions & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ and `/openapi` are matched first.
| `POST /v1/model/threat-register` | Model | Split the register by origin and standing: manual, current-generated, stale-generated, and entries whose rule was not part of the run. |
| `POST /v1/model/read` | Model | Parse uploaded bytes (base64) into the canonical model. |
| `POST /v1/model/manifest` | Model | Materialize a declarative authoring manifest into a model (the `tmforge apply` build). |
| `POST /v1/model/layout` | Model | Return geometry-only updates after preserving every boundary membership and actual flow crossing; unsafe candidates are refused atomically. |
| `POST /v1/model/convert?to=<format>` | Model | Convert a model to another format. |
| `POST /v1/model/export/tm7` | Model | Export a model as a `.tm7` file. |
| `POST /v1/model/report?format=<html\|svg>` | Report | Render a model to an HTML or SVG report. |
Expand Down Expand Up @@ -93,6 +94,45 @@ curl -s -X POST http://localhost:8080/v1/model/analysis \
combined action returns for their half; they exist for callers that genuinely need only one
projection, and they do not materialize the other.

## Shared arrangement

`POST /v1/model/layout` accepts a `LayoutRequestDto`: `model` is the **original** canonical model;
optional `page` selects a name or one-based index (otherwise every page); and optional `options`
supplies layout metrics such as
`nodeSpacing`, `layerSpacing`, `maxWidth`, and `boundaryHeaderHeight`.

Without `positions`, the engine generates a bounded arrangement, as used by the CLI's explicit
layout command. `maxWidth` defaults to `1760` for its MTMT-oriented layout.

For in-place cleanup, supply `positions: [{id,x,y,width,height}, ...]` for every element on the
selected pages. The engine validates those exact rectangles against the original
model's memberships and crossings, recomputing connector endpoints but never re-layering the
candidate. Partial, duplicate, unknown or out-of-bounds positions are refused.
Studio's one-click Tidy uses only this validation path with the
released cleanup algorithm; Studio does not request engine-generated rearrangement.

Include text fitting in the proposed positions, not by resizing the input model first: the original geometry is
the baseline against which memberships and crossings must be preserved. Unknown or duplicate ids,
cross-page/dangling flows, boundary-ended flows, invalid geometry and excessive work are refused rather
than normalized into a different model. No analysis rules are evaluated, and property bags and
triage are not hydrated into the geometry candidate.

The response is `{success,error,elements,pages,components,labelOverlaps}`. On success, `elements`
contains only `{id,x,y,width,height}` keyed by the original author ids. Apply those rectangles to the
existing document; do not replace it. Pages, topology, properties, rule selections, triage and view
state remain owned by the caller. A refusal returns HTTP `200` with `success: false`, a reason, and
an empty `elements` array; malformed request JSON still receives the ordinary `400` problem response.

All requested pages succeed or none do. The engine checks complete component membership, both
connector endpoints' boundary sides, and crossing sets using the analyzer's boundary implementation.
Overlapping regions are not assumed to be invalid; an arrangement that would drop a claim is refused.
There is no force bypass. See the [CLI layout limits](cli-reference.md#layout) for computation bounds;
canonical model dimensions and proposed rectangles must be at least 20 units in width and height.

The WASM `Layout(requestJson)` export returns the identical contract. Studio retains client-side
text measurement when computing the candidate, then visual routing and label deconfliction after
the shared validation step.

## Custom rule packs

Custom rules are deployment configuration, not request input: this host never loads rules from a
Expand Down
19 changes: 19 additions & 0 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,24 @@ arrangement that moved a component out of its boundary would change what the mod
how it looks. Columns wrap onto a new row instead of running past the right-hand edge, because the
Microsoft Threat Modeling Tool's drawing surface is bounded and taller than it is wide.

Arrangement is **validated before it is committed**. Every component keeps its complete set of
boundary memberships, each connector endpoint stays on the same side of every boundary, and actual
crossing sets (including line trust boundaries) must be unchanged. If any selected page fails, the
command exits `1` and writes nothing. Partially overlapping claims are not discarded, and flows
attached to a boundary rather than a component are refused. Use `--labels` to keep such geometry,
or resolve the placement explicitly; there is no force flag that silently changes trust claims.

Spacing arguments must be integers from `1` to `4096`. An arrangement request is limited to 32 pages,
512 shapes and 1,024 lines, with a separate 25-million-work-unit bound for dense graphs and label
placement. Shape sizes must be positive and at most 100,000 units, and input/output coordinates must
be within ±1,000,000. These are computation limits, not a promise that an oversized diagram fits
the MTMT drawing surface.

On canonical JSON, layout patches only the selected rectangles and retains author ids, flow
aliases, properties, analysis settings, triage, and unknown extension/view fields. JSON does not
persist the engine's connector handles: `--labels` leaves it unchanged, and `labelsPersisted: false`
in JSON output distinguishes that from a persisted label edit. Studio handles its own label offsets.

```text
tmforge layout [--page <name|index>] [--node-spacing <n>] [--layer-spacing <n>] [--labels] [--check] [--json] <model>
```
Expand All @@ -529,6 +547,7 @@ tmforge layout [--page <name|index>] [--node-spacing <n>] [--layer-spacing <n>]
| --- | --- |
| `--labels` | Place only the flow labels and leave every shape exactly where it is. Use this when the geometry is hand-placed or comes from a manifest and only the labels need sorting out. |
| `--check` | Report obstructed flow labels and write nothing. Exits `1` when any remain, so a publishing gate can require a legible diagram. |
| `--page` | Arrange one page by name or one-based index. Omitted means every page; refusal is atomic across the selected pages. |

```bash
tmforge layout payments.tm7
Expand Down
34 changes: 30 additions & 4 deletions docs/studio-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,36 @@ connection, not the geometry.
| Select several objects | Hold `Cmd` (`Ctrl` on Windows/Linux) and click, or drag a selection box with `Shift`. |
| Delete the selection | `Delete` key. Deleting an element takes its flows with it, and the whole deletion is a single undo step. |
| Resize a trust boundary | Drag its handles (it's a resizable region). |
| Tidy the diagram | Click **Tidy** to fit labels, separate overlapping shapes and peer trust boundaries, route flows, and deconflict flow labels. Each boundary also keeps a clear strip at its top so nothing covers its own name. Nested boundaries remain nested, and each boundary moves with its members. |
| Tidy the diagram | Click **Tidy** to clean up the existing arrangement: fit text, separate overlaps, route flows and deconflict labels. The adjacent **Tidy options** menu offers offline **Labels only** with fixed rectangles. |
| Pan / zoom | Drag the canvas / scroll; use the minimap and **fit** control to navigate. |
| Step through the flows | `Alt+↓` / `Alt+↑` selects the next / previous flow in the outline's order. |
| Undo / redo | `Cmd+Z` / `Shift+Cmd+Z` (covers every edit). |

Arrangement applies to the **active page**, as one undo step. A failed or unsafe candidate changes
nothing and consumes no undo step. If an edit or page switch occurs while the engine is working,
the response is discarded rather than overwriting newer work. Repeating an unchanged arrangement is
a no-op.

**Tidy preserves the author's arrangement**, using the released Studio cleanup algorithm. It does
not move an already horizontal sketch into graph layers or shrink boundaries around newly ordered
groups. Objects move only as text fitting and overlap separation require. The engine validates the
proposed rectangles without rearranging them again, so the boundary-safety checks still apply.

Studio does not offer full layout rearrangement. The CLI's explicit `layout` command remains
available for callers that want to generate placement rather than tidy an existing drawing.

The engine preserves the **actual geometric trust claims**, including all memberships when regions
overlap; it never chooses one claim and discards another. If the candidate cannot preserve them,
Studio explains the refusal. Use **Labels only**, or edit the boundary placement explicitly before
trying again. Opening a file now preserves its shape and boundary rectangles: automatic import
cleanup is limited to visual routing and label offsets. Text-fit sizing requires an explicit
arrangement so importing a model cannot silently change analysis.

A single-page model can still contain a detached flow. An imported connector whose source or target
id is all zeros is not attached to an element. Tidy names the affected flow and endpoint; reconnect
it in the source model, or remove the flow if it is unintended. Tidy never guesses the missing target
or silently drops the connection. **Labels only** remains available without rearranging shapes.

### Pages

A model can hold several diagrams. The **page tab strip** below the canvas lets you work across them:
Expand Down Expand Up @@ -104,9 +129,10 @@ the listed order, wrapping at either end, so a review can be worked through flow
hunted for. Each step also opens that flow in the inspector, so its properties are right there while
you read it.

An object is placed in a boundary by its authored `Boundary` property when it has one, and otherwise
by the smallest boundary region it sits inside — the same rule **Tidy** uses, so the list and the
drawing always agree.
The outline groups an object by its authored `Boundary` property when it has one, and otherwise by
the smallest boundary region containing its center. Arrangement instead preserves the complete
geometry-derived membership set used for analysis. If a declared property disagrees with the
drawing, resolve that disagreement explicitly rather than relying on Tidy to change the trust claim.

### The inspector

Expand Down
5 changes: 5 additions & 0 deletions src/ThreatModelForge.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ public static void Main(string[] args)
request.Theirs ?? new TmForgeModelDto())))
.WithName("MergeModels")
.WithTags("Model");
app.MapPost(
"/v1/model/layout",
(LayoutRequestDto request) => TypedResults.Ok(EngineService.Layout(request)))
.WithName("LayoutModel")
.WithTags("Model");
app.MapPost(
"/v1/model/export/tm7",
(TmForgeModelDto model) => TypedResults.File(EngineService.ExportTm7(model, rules), "application/xml", "model.tm7"))
Expand Down
Loading
Loading